KatsuYuzuのブログ

.NET の開発をメインとした日記です。

縦横のレイアウト変更を自動化するビヘイビアー #wpdev_jp

Accent Color Cameraアプリを更新しました。カラーをリアルタイムに採取できるようになりました。
f:id:KatsuYuzu:20130406235954p:plain:w320

縦画面にも対応したのですが、そのときに得たTIPSです。

概要

アプリで縦横対応すると向きに合わせてデザインを変えたいですよね。それとカメラを処理しているとカメラの向き補正が必須だったりも。


つぶやいていたらMSエバンジェリストの高橋さんが反応してくださって翌日あたりにはTIPSを記事にまで!ありがとうございました。

ここから取り込んで自分なりにブラッシュアップさせてみたお話です。

完成形

VisualStateManagerで各向きの状態を定義してビヘイビアーで指定することで、端末の向きが変わったときに自動で状態を遷移させてくれます。
f:id:KatsuYuzu:20130407002653p:plain

OrientationChangedとLoaded

端末の向きがかわったときだけだと起動時の画面がちぐはぐになるのでLoadedに対応させておきます。

        protected override void OnAttached()
        {
            base.OnAttached();

            this.AssociatedObject.Loaded += this.AssociatedObject_Loaded;
            this.AssociatedObject.OrientationChanged += this.AssociatedObject_OrientationChanged;
        }

        protected override void OnDetaching()
        {
            this.AssociatedObject.Loaded -= this.AssociatedObject_Loaded;
            this.AssociatedObject.OrientationChanged -= this.AssociatedObject_OrientationChanged;

            base.OnDetaching();
        }

        void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
        {
            this.GoToState(this.AssociatedObject.Orientation);
        }

        void AssociatedObject_OrientationChanged(object sender, OrientationChangedEventArgs e)
        {
            this.GoToState(e.Orientation);
        }

CustomPropertyValueEditorAttribute

普通にPropertyを定義すると以下のようになりState名を入力しないといけません。
f:id:KatsuYuzu:20130407003730p:plain
PropertyにCustomPropertyValueEditorAttributeをつけることでVisualStateManagerに定義されたStateから選択できるようになります。

        #region PortraitUpState
        /// <summary>
        /// 縦長の向きの場合の状態名を取得または設定します。
        /// </summary>
        [CustomPropertyValueEditor(CustomPropertyValueEditor.StateName)]
        public string PortraitUpState
        {
            get { return (string)GetValue(PortraitUpStateProperty); }
            set { SetValue(PortraitUpStateProperty, value); }
        }

        // Using a DependencyProperty as the backing store for PortraitUpState.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PortraitUpStateProperty =
            DependencyProperty.Register("PortraitUpState", typeof(string), typeof(OrientationStateBehavior), new PropertyMetadata(null));
        #endregion

f:id:KatsuYuzu:20130407002653p:plain
余談ですが、DependencyPropertyのこの長ったらしい定義、propdpまで入力してTabキー叩けばでてきますよ。コードスニペット便利!それとCustomPropertyValueEditorにはほかにもバインディング周りのサポートがありますので頭の片隅に。
CustomPropertyValueEditor 列挙 (System.Windows.Interactivity)

ビヘイビアー全体

少し長くなるのでgistにも投稿しました。

using Microsoft.Expression.Interactivity;
using Microsoft.Phone.Controls;
using System.Windows;
using System.Windows.Interactivity;
 
namespace KatsuYuzu.Interactivity
{
    /// <summary>
    /// ページの向きを基に状態を切り替えます。
    /// </summary>
    public sealed class OrientationStateBehavior : Behavior<PhoneApplicationPage>
    {
        #region PortraitUpState
        /// <summary>
        /// 縦長の向きの場合の状態名を取得または設定します。
        /// </summary>
        [CustomPropertyValueEditor(CustomPropertyValueEditor.StateName)]
        public string PortraitUpState
        {
            get { return (string)GetValue(PortraitUpStateProperty); }
            set { SetValue(PortraitUpStateProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for PortraitUpState.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PortraitUpStateProperty =
            DependencyProperty.Register("PortraitUpState", typeof(string), typeof(OrientationStateBehavior), new PropertyMetadata(null));
        #endregion
 
        #region LandscapeLeftState
        /// <summary>
        /// ページの上部を左に回転させた横向きの場合の状態名を取得または設定します。
        /// </summary>
        [CustomPropertyValueEditor(CustomPropertyValueEditor.StateName)]
        public string LandscapeLeftState
        {
            get { return (string)GetValue(LandscapeLeftStateProperty); }
            set { SetValue(LandscapeLeftStateProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for LandscapeLeftState.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LandscapeLeftStateProperty =
            DependencyProperty.Register("LandscapeLeftState", typeof(string), typeof(OrientationStateBehavior), new PropertyMetadata(null));
        #endregion
 
        #region LandscapeRightState
        /// <summary>
        /// ページの上部を右に回転させた横向きの場合の状態名を取得または設定します。
        /// </summary>
        [CustomPropertyValueEditor(CustomPropertyValueEditor.StateName)]
        public string LandscapeRightState
        {
            get { return (string)GetValue(LandscapeRightStateProperty); }
            set { SetValue(LandscapeRightStateProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for LandscapeRightState.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LandscapeRightStateProperty =
            DependencyProperty.Register("LandscapeRightState", typeof(string), typeof(OrientationStateBehavior), new PropertyMetadata(null));
        #endregion
 
        #region UseTransitions
        /// <summary>
        /// System.Windows.VisualTransition を使用して状態を切り替える場合は true、それ以外は false。
        /// </summary>
        public bool UseTransitions
        {
            get { return (bool)GetValue(UseTransitionsProperty); }
            set { SetValue(UseTransitionsProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for UseTransitions.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UseTransitionsProperty =
            DependencyProperty.Register("UseTransitions", typeof(bool), typeof(OrientationStateBehavior), new PropertyMetadata(true));
        #endregion
 
        protected override void OnAttached()
        {
            base.OnAttached();
 
            this.AssociatedObject.Loaded += this.AssociatedObject_Loaded;
            this.AssociatedObject.OrientationChanged += this.AssociatedObject_OrientationChanged;
        }
 
        protected override void OnDetaching()
        {
            this.AssociatedObject.Loaded -= this.AssociatedObject_Loaded;
            this.AssociatedObject.OrientationChanged -= this.AssociatedObject_OrientationChanged;
 
            base.OnDetaching();
        }
 
        void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
        {
            this.GoToState(this.AssociatedObject.Orientation);
        }
 
        void AssociatedObject_OrientationChanged(object sender, OrientationChangedEventArgs e)
        {
            this.GoToState(e.Orientation);
        }
 
        /// <summary>
        /// 状態を切り替えます。
        /// </summary>
        /// <param name="orientation"></param>
        void GoToState(PageOrientation orientation)
        {
            string stateName;
 
            switch (orientation)
            {
                case PageOrientation.PortraitUp:
                    stateName = this.PortraitUpState;
                    break;
                case PageOrientation.LandscapeLeft:
                    stateName = this.LandscapeLeftState;
                    break;
                case PageOrientation.LandscapeRight:
                    stateName = this.LandscapeRightState;
                    break;
                default:
                    stateName = string.Empty;
                    break;
            }
 
            VisualStateUtilities.GoToState(this.AssociatedObject, stateName, this.UseTransitions);
        }
    }
}