UWPプラットフォームでコントロールを拡張、変更、および作成します。 パート1





2006年、.NET 3.0とともに、開発者はWPFおよびSilverlightソフトウェアプラットフォームを提供されました。 次の10年間で、Microsoftはオペレーティングシステムとそれぞれのプラットフォームの新しいバージョンをリリースしました。 そのため、2016年にWindows 10と共に、ユニバーサルWindowsプラットフォームがリリースされました。



すべてのプラットフォームは、APIの機能によって程度が異なりますが、それらの共通のXAMLマークアップ言語はすべてほとんど変更されていません。 したがって、どのプラットフォームで作業するかに関係なく、すべての開発者は同じタスクに直面します。つまり、既存のものを拡張または変更し、新しいコントロールを開発します。 これらは、設計と機能の要件を満たすアプリケーションを開発するために必要な非常に重要なスキルです。



これらのタスクは、どのプラットフォームでも、開発者がアプリケーション開発に必要なコントロールのセットが限られているという事実によるものです。 そのツールは、Microsoft(UWP-Windows Universal Platform SDKの場合)およびサードパーティのサプライヤまたは開発者からの要素です。 集合的にも、アプリケーション開発中に現れるすべての要件をカバーすることはできません。 使用可能なコントロールは、外観、動作、または機能などのいくつかの理由により、あなたに合わない場合があります。 残念ながら、今日まで、これらの問題の解決策を包括的かつ容易に網羅する単一の情報源はありません。 開発者にとって長い間残っているのは、インターネット上で少しずつ情報を収集することです。



この3つの記事のシリーズの目的は、新しいコントロールを変更、拡張、作成する方法を体系化することです。



パート1.既存のコントロールの拡張



最初の部分では、既存のコントロールの内部構造を妨げることなく拡張することについて説明します。



コントロールの一般的な動作と機能は開発者に合っているが、拡張する必要があると仮定します。 たとえば、 TextBoxコントロールにはデータを入力する機能がありますが、検証の機能はありません。 目的の結果を取得する最も簡単な方法は、このTextBoxを含む分離コードビュー(ビュー)にロジックを追加することです。



public sealed partial class MainPage : Page { public MainPage () { this.InitializeComponent (); textbox.TextChanged += Textbox_TextChanged; } private void Textbox_TextChanged (object sender, TextChangedEventArgs e) { // Some validation logic } }
      
      





ただし、UWP開発にはMVVMアーキテクチャパターンの使用が含まれ、その主な目標の1つは、ロジックをプレゼンテーションから分離することです。 したがって、ビューのViewModelまたはMVVMの原則に違反することなくブラックボックスとして対話する新しいコントロールのいずれかにカプセル化する必要があります。



したがって、上記のリストに示されているソリューションは、開発者の怠greatが大きい場合にのみ適しています。開発者は、開発の後続の段階で彼に戻ってこないことを確信しています。 アプリケーション内でこのような検証が複数の場所で必要な場合、これはこのロジックを別のエンティティに取り込む必要があるという直接的な兆候です。



内部構造と機能を妨げることなくコントロールを拡張する方法は2つあり、その実装は寄生と比較できます-プロパティと動作を添付します。



添付プロパティ



添付プロパティは、別のクラスで定義され、XAMLレベルでターゲットに添付される依存関係プロパティの一種です。



上記の登録ページのTextBox検証の例で、添付プロパティの操作のメカニズムを考えてみましょう。





無効で有効な登録フォーム



次の添付プロパティを含むTextBoxExtensionsクラスを定義します。



1. RegexPattern -RegEx検証パターン文字列を入力として受け取るプロパティ。 行が空の場合、入力フィールドの検証は必要ないと考えられます。

2. IsValid - RegexPatternプロパティで指定されたテンプレートに基づいた入力フィールドの現在の検証ステータスの値を含むプロパティ。



このクラスには、 RegexPatternプロパティの値が変更されたときに起動するOnRegexPatternChangedメソッドも含まれます 。 値が空でない場合、 RegexPatternプロパティとIsValidプロパティが機能するコンテキストで、TextBoxコントロールのTextChangedイベントにサブスクライブします。



Textbox_TextChangedイベントハンドラーで、渡されたパターンに対して文字列を検証するValidateTextメソッド呼び出します。 その結果をIsValidプロパティに割り当てます。



 public class TextBoxExtensions { public static string GetRegexPattern (DependencyObject obj) { return (string) obj.GetValue (RegexPatternProperty); } public static void SetRegexPattern (DependencyObject obj, string value) { obj.SetValue (RegexPatternProperty, value); } public static readonly DependencyProperty RegexPatternProperty = DependencyProperty.RegisterAttached ("RegexPattern", typeof (string), typeof (TextBoxExtensions), new PropertyMetadata (string.Empty, OnRegexPatternChanged)); public static bool GetIsValid (DependencyObject obj) { return (bool) obj.GetValue (IsValidProperty); } public static void SetIsValid (DependencyObject obj, bool value) { obj.SetValue (IsValidProperty, value); } public static readonly DependencyProperty IsValidProperty = DependencyProperty.RegisterAttached ("IsValid", typeof (bool), typeof (TextBoxExtensions), new PropertyMetadata (true)); private static void OnRegexPatternChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) { var textbox = d as TextBox; if (textbox == null) { return; } textbox.TextChanged -= Textbox_TextChanged; var regexPattern = (string) e.NewValue; if (string.IsNullOrEmpty (regexPattern)) { return; } textbox.TextChanged += Textbox_TextChanged; SetIsValid (textbox, ValidateText (textbox.Text, regexPattern)); } private static void Textbox_TextChanged (object sender, TextChangedEventArgs e) { var textbox = sender as TextBox; if (textbox == null) { return; } if (ValidateText (textbox.Text, GetRegexPattern (textbox))) { SetIsValid (textbox, true); } else { SetIsValid (textbox, false); } } private static bool ValidateText (string text, string regexPattern) { if (Regex.IsMatch (text, regexPattern)) { return true; } return false; } }
      
      





次に、これらのプロパティをマークアップの入力フィールドにバインドし、 RegexPatternプロパティの値を設定します。



 <TextBox Grid.Row="1" Grid.Column="1" ap:TextBoxExtensions.RegexPattern="." ap:TextBoxExtensions.IsValid="{x:Bind ViewModel.IsUserNameValid, Mode=TwoWay}" IsSpellCheckEnabled="False"/> <TextBox Grid.Row="2" Grid.Column="1" ap:TextBoxExtensions.RegexPattern="^\d{2}\.\d{2}\.\d{4}$" ap:TextBoxExtensions.IsValid="{x:Bind ViewModel.IsBirthdateValid, Mode=TwoWay}"/> <TextBox Grid.Row="3" Grid.Column="1" ap:TextBoxExtensions.RegexPattern="^([\w\.\-]+)@([\w\-]+)((\.(\w){2,4})+)$" ap:TextBoxExtensions.IsValid="{x:Bind ViewModel.IsEmailValid, Mode=TwoWay}" IsSpellCheckEnabled="False"/> <PasswordBox Grid.Row="4" Grid.Column="1" ap:PasswordBoxExtensions.RegexPattern="." ap:PasswordBoxExtensions.IsValid="{x:Bind ViewModel.IsPasswordValid, Mode=TwoWay}" />
      
      





きれいなコードビハインドがあります。



 public sealed partial class RegistrationView : UserControl { public RegistrationViewModel ViewModel { get; private set; } public RegistrationView () { this.InitializeComponent (); this.DataContext = ViewModel = new RegistrationViewModel (); } }
      
      





また、ViewModelレベルでの登録ボタンのアクセシビリティのロジック。



 public class RegistrationViewModel : BindableBase { private bool isUserNameValid = false; public bool IsUserNameValid { get { return isUserNameValid; } set { Set (ref isUserNameValid, value); RaisePropertyChanged (nameof (IsRegisterButtonEnabled)); } } private bool isBirthdateValid = false; public bool IsBirthdateValid { get { return isBirthdateValid; } set { Set (ref isBirthdateValid, value); RaisePropertyChanged (nameof (IsRegisterButtonEnabled)); } } private bool isEmailValid = false; public bool IsEmailValid { get { return isEmailValid; } set { Set (ref isEmailValid, value); RaisePropertyChanged (nameof (IsRegisterButtonEnabled)); } } private bool isPasswordValid = false; public bool IsPasswordValid { get { return isPasswordValid; } set { Set (ref isPasswordValid, value); RaisePropertyChanged (nameof (IsRegisterButtonEnabled)); } } public bool IsRegisterButtonEnabled { get { return IsUserNameValid && IsBirthdateValid && IsEmailValid && IsPasswordValid; } } }
      
      





PasswordBoxExtensionsクラスのリストは、 TextBoxExtensionsクラスを完全に少しずつ繰り返し、両方のコントロールが共通のフィールドとイベントを受け取ることができる抽象的なTextInputクラスから継承されず、あまりにも一般的なControlクラスから継承されるという理由でのみ存在します。



添付プロパティのおかげで、既存のTextBoxクラスとPasswordBoxクラスの機能を、内部構造に干渉することなく拡張できました。 そして、それらから新しい子孫クラスを生成する必要さえありませんでしたが、これは常に可能とは限りません。



行動



Expression Blend 3には、アニメーション、視覚効果、ドラッグアンドドロップなど、ユーザーインターフェイス側で発生するタスクを解決するメカニズムを開発者に提供することを目的とした動作が登場しました。



UWPは、動作ライブラリを同梱していません。 Expression Blend SDKの一部として、たとえばNugetを使用して個別にインストールする必要があります。



FlipViewコントロールを使用しており、スクロールするときに新しい要素が外観アニメーションを再現する必要があるとします。





行動アニメーション



BehaviorTクラスから継承されたFlipViewItemFadeInBehaviorクラスを定義します。Tは、必要な動作を追加できるクラスまたはその子孫のクラスの名前です。



その中で、 OnAttachedメソッドを再定義します。このメソッドでは、 FlipView型の関連オブジェクトのSelectionChangedイベントにサブスクライブします。



FlipView_SelectionChangedイベントハンドラーで、必要なアニメーションを新しい要素にアタッチして開始します。 Durationプロパティを定義することにより、アニメーションの再生時間をパラメーター化できます。



 public class FlipViewItemFadeInBehavior : Behavior<FlipView> { public double Duration { get; set; } protected override void OnAttached () { base.OnAttached (); AssociatedObject.SelectionChanged += FlipView_SelectionChanged; } protected override void OnDetaching () { base.OnDetaching (); AssociatedObject.SelectionChanged -= FlipView_SelectionChanged; } private void FlipView_SelectionChanged (object sender, SelectionChangedEventArgs e) { var flipView = sender as FlipView; var selectedItem = flipView.SelectedItem as UIElement; Storyboard sb = new Storyboard (); DoubleAnimation da = new DoubleAnimation { Duration = new Duration (TimeSpan.FromSeconds (Duration)), From = 0d, To = 1d }; Storyboard.SetTargetProperty (da, "(UIElement.Opacity)"); Storyboard.SetTarget (da, selectedItem); sb.Children.Add (da); sb.Begin (); } }
      
      





これで、マークアップの必要なコントロールにこの動作を追加する準備ができました。



 xmlns:b="using:ArticleSandbox.Controls.Behaviors" xmlns:i="using:Microsoft.Xaml.Interactivity" <FlipView HorizontalAlignment="Center" VerticalAlignment="Center"> <FlipView.Items> <Rectangle Fill="Red" Width="200" Height="100"/> <Rectangle Fill="Green" Width="200" Height="100"/> <Rectangle Fill="Blue" Width="200" Height="100"/> </FlipView.Items> <i:Interaction.Behaviors> <b:FlipViewItemFadeInBehavior Duration="2"/> </i:Interaction.Behaviors> </FlipView>
      
      





したがって、アニメーションロジックを別のエンティティにまとめ、マークアップの定義を通じて使用できるようにしました。



ここで説明するメカニズムは両方とも既存のコントロールを拡張するものであり、いつどのメカニズムを使用するかを明確に理解することが重要です。



何らかのロジックを使用してコントロールを拡張する必要がある場合、これは、添付プロパティを通じて結果を達成できることを示しています。 コントロールが何らかの視覚効果またはアニメーションを提供する必要がある場合、動作のメカニズムに注意を払う価値があります。



2番目のパート「 既存のコントロールの変更 」を読み続けてください。



Ian Moroz、シニア.NET開発者



All Articles