MVVMパターンに関する最新の進歩的な資料は、 Aero Frameworkを介したContext Model Patternの記事で紹介されており、 次の一連の記事で詳しく説明されています。
この記事では、Windows Phone用の複雑ではなく、インタラクティブで機能的なグラフィカルな描画エディターの作成方法について説明します。 経験豊富な開発者でさえ、自分自身にとって興味深い、新しい何かを見つけることができると思います。 エディターのユニークな機能は、スライダースライダーを使用して文字通り適切なタイミングで巻き戻すことができるストーリーです。 そして、はい、結論として、私たちは虹を描きます! 行こう...
もちろん、 定性的な例を用意しました。
マークアップ拡張機能
WinPhoneでの開発を始めたとき、このプラットフォームの多くの制限にすぐに失望しました。 たとえば、WPFやSilverlightのように、ここには通常のマークアップ拡張機能すらありませんでした。 実際、たとえば、ローカライズまたは写真の場合、次のコードをxamlで記述する方がはるかに美しいです。
<TextBlock Text={Localizing Hello}/> <Button Content={Picture New.png}/>
「どう? とても便利なことだと思いました」と私は考えました。暇なときに、この問題を無駄にではなく詳細に調査することにしました。
ライブラリクラスの再構築者から逆アセンブラを調べてみると、Bindingクラスがシールド属性でマークされていないことに突然気付きました。 しかし、もし彼から受け継いだら、私の頭の中にひらめいたとしたらどうでしょう? 試したところ、うまくいきました!
public abstract class MarkupExtension : Binding, IValueConverter { protected MarkupExtension() { Source = Converter = this; } protected MarkupExtension(object source) // set Source to null for using DataContext { Source = source; Converter = this; } protected MarkupExtension(RelativeSource relativeSource) { RelativeSource = relativeSource; Converter = this; } public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture); public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
ここに基本クラスがあります。 次に、ローカライズ用の拡張機能を実装する例。
public class Localizing : MarkupExtension { public static readonly LocalizingManager Manager = new LocalizingManager(); public Localizing() { Source = Manager; Path = new PropertyPath("Source"); } public string Key { get; set; } public override string ToString() { return Convert(Manager.Source, null, Key, Thread.CurrentThread.CurrentCulture) as string ?? string.Empty; } public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var key = Key; var resourceManager = value as ResourceManager; if (resourceManager == null || string.IsNullOrEmpty(key)) return ":" + key + ":"; var localizedValue = resourceManager.GetString(key); return localizedValue ?? ":" + key + ":"; } } public class LocalizingManager : INotifyPropertyChanged { private ResourceManager _source; public ResourceManager Source { get { return _source; } set { _source = value; PropertyChanged(this, new PropertyChangedEventArgs("Source")); } } public event PropertyChangedEventHandler PropertyChanged = (sender, args) => { }; }
これでWinPhoneで書くことができます
<TextBlock Text="{f:Localizing Key=ApplicationTitle}"/>
残念ながら、プレフィックスfを取り除くことはできませんでした。プラットフォームの制限により、Keyプロパティの名前を指定することも必須ですが、これはすでに標準のエントリよりも優れています
<TextBlock Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}"/>
ところで、私たちの実装は、オンザフライと呼ばれる、アプリケーションの実行中の言語のホットスワップをサポートしています。 MarkupExtension基本クラスを使用すると、さらに興味深いことができます。 このためのコード例を参照することをお勧めします。
潜在的な落とし穴に対する注意。 MarkupExtension(RelativeSource relativeSource)コンストラクターを使用する場合、バインドしているコントロールはvalueパラメーターのConvertメソッドに送られます。 コントロールを使用すると、任意の操作を実行できますが、ハードリンクを保存すると、メモリリークが発生する可能性があります(たとえば、拡張機能がクラスの静的インスタンスに関連付けられている場合、インターフェイスの一部はガベージコレクションから保持されますが、ビューが閉じられ、不要な場合)。 したがって、そのような目的には遅延リンク(WeakReferences)を使用してください。
共有コマンド
WinPhoneのチームでは物事はあまり良くないので、自分で実装する必要があります。 さらに、コントロールはCanExecuteChangedイベントをサブスクライブするため、ここでも注意する必要があります。このサブスクリプションが読み書きできない場合、同じメモリリークが発生する可能性があります。 私自身はこのニュアンスに注意を払っていませんでしたが、友人であり優秀な開発者のユーリ・カリノフは私にそれを指摘しました。 この問題については、 こちらをご覧ください 。
WPFにビジネスがあるかどうかにかかわらず、RoutedCommandsとCommandBindingsの優れたメカニズムがあります。 前の記事で 、どのように美しく使用できるかについて話しました。 しかし、WinPhoneに同様のものを実装するとどうなりますか? このタスクに夜を捧げながら、それにもかかわらず特定の結果を達成し、共有コマンドの概念を実現しました。 これらはビジュアルツリーを介してルーティングされませんが、アプリケーションのタスクには最適です。 たとえば、すぐにリーダーを送信して、実装を確認します。ここでは、それらの使用方法を説明します。 すべてがシンプルで便利です。モデルの観点では、次のように書きます。
this[SharedCommands.Back].CanExecute += (sender, args) => args.CanExecute = TouchIndex > 0; this[SharedCommands.Next].CanExecute += (sender, args) => args.CanExecute = TouchIndex < _toches.Count; this[SharedCommands.Back].Executed += (sender, args) => TouchIndex--; this[SharedCommands.Next].Executed += (sender, args) => TouchIndex++;
そして、プレゼンテーションは以下についてです
<Button Command="{f:Command Key=Back}" Content="{f:Picture Key=/Resources/IconSet/Next.png, Width=32, Height=32}"/> <Button Command="{f:Command Key=Next}" Content="{f:Picture Key=/Resources/IconSet/Next.png, Width=32, Height=32}"/>
ラムダ式
ラムダ式を使用してビューモデルのプロパティに通知することは古典的です
public Tool Tool { get { return Get(() => Tool); } set { Set(() => Tool, value); } }
インデクサーをリロードすることも非常に便利です
this[() => Background].PropertyChanged += (sender, args) => { Canvas.Children.Clear(); Canvas.Background = Background; _toches.GetRange(0, TouchIndex).ForEach(Canvas.Children.Add); };
他の誰かがそのような構造の速度について懸念を持っている場合、私はそれらを払拭することを敢えてします-すべてが迅速に動作します。 Skype for WinPhoneのクライアントバージョンの逆アセンブルされたソースコードをハブに配置すると、これらのソースがすぐに削除されたことを覚えていますが、それらをダウンロードして、ビューモデルの動作を確認しました。 これはもちろんインジケーターではありませんが、プロパティを通知するためにラムダ式を使用したのと同じです。 Microsoftの賢い人たちはSkypeで働いていたと思うので、彼らにはある程度の信頼がある。 そして、Skypeのソースはコンピューターから削除されました;)
描画
私たちは、最も興味深いことに描画-に来ました。 プラットフォームのモビリティを考慮して整理する最良の方法は何ですか? 私の意見では、最も簡単で最も論理的な方法は、標準ツール、つまりCanvas、Polylineなどのプリミティブを使用することです。 プロセッサのグラフィックコアまたはSIMD命令を使用してインターフェイスをレンダリングするため、WritableBitmapなどに基づく自転車ではなく、なぜそれらを推奨するのですか?しかし、手書きを行う場合、パフォーマンスを低下させる通常のプロセッサ命令に負荷をシフトするだけです開発を著しく複雑にします。
メカニズムの本質は次のとおりです。 インターフェイスに表示されるキャンバス(
Canvas
)があり、そのタッチは特定のブラシでのブラシストロークとして解釈されます。
Canvas.Children
ごとにプリミティブが作成され、
Canvas.Children
コレクションに追加されます。 しかし、これらのストロークが何千もある場合、これはパフォーマンスに影響しますか? はい、それは大幅に反映されるため、少なくとも時々画像をラスタライズする必要があります。つまり、
Canvas.Children
をクリアして、現時点で判明した画像をCanvas.Backgroundにインストールする必要があります。 こんな感じ
var raster = new WriteableBitmap(Canvas, null); Canvas.Background = new ImageBrush { AlignmentX = AlignmentX.Left, AlignmentY = AlignmentY.Top, Stretch = Stretch.None, ImageSource = raster, }; Canvas.Children.Clear();
この例のラスタライズは、新しいプリミティブを追加する前に毎回発生しますが、ラスタライズすると失われるため、ストーリーをどのように整理しますか? ここでも複雑なことはありません-描画の開始前のキャンバスの元の背景を保存し、
List _toches . , _toches Canvas.Children
, _toches
.
: Polyline OpacityMask Canvas. , .
(). , .
, , . , , .
<LinearGradientBrush x:Key="RainbowBrush" StartPoint="0 0" EndPoint="0 0.6"> <LinearGradientBrush.Transform> <ScaleTransform ScaleY="2.8"/> </LinearGradientBrush.Transform> <LinearGradientBrush.GradientStops> <GradientStop Color="#FFFF0000" Offset="0.0"/> <GradientStop Color="#FFFFFF00" Offset="0.1"/> <GradientStop Color="#FF00FF00" Offset="0.2"/> <GradientStop Color="#FF00FFFF" Offset="0.3"/> <GradientStop Color="#FF0000FF" Offset="0.4"/> <GradientStop Color="#FFFF00FF" Offset="0.5"/> <GradientStop Color="#FFFF0000" Offset="0.6"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush>
, – , , . MVVM: . , , , .
!
PS . , , , , . .
コレクション
List _toches . , _toches Canvas.Children
, _toches
.
: Polyline OpacityMask Canvas. , .
(). , .
, , . , , .
<LinearGradientBrush x:Key="RainbowBrush" StartPoint="0 0" EndPoint="0 0.6"> <LinearGradientBrush.Transform> <ScaleTransform ScaleY="2.8"/> </LinearGradientBrush.Transform> <LinearGradientBrush.GradientStops> <GradientStop Color="#FFFF0000" Offset="0.0"/> <GradientStop Color="#FFFFFF00" Offset="0.1"/> <GradientStop Color="#FF00FF00" Offset="0.2"/> <GradientStop Color="#FF00FFFF" Offset="0.3"/> <GradientStop Color="#FF0000FF" Offset="0.4"/> <GradientStop Color="#FFFF00FF" Offset="0.5"/> <GradientStop Color="#FFFF0000" Offset="0.6"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush>
, – , , . MVVM: . , , , .
!
PS . , , , , . .
開始して
List _toches . , _toches Canvas.Children
, _toches
.
: Polyline OpacityMask Canvas. , .
(). , .
, , . , , .
<LinearGradientBrush x:Key="RainbowBrush" StartPoint="0 0" EndPoint="0 0.6"> <LinearGradientBrush.Transform> <ScaleTransform ScaleY="2.8"/> </LinearGradientBrush.Transform> <LinearGradientBrush.GradientStops> <GradientStop Color="#FFFF0000" Offset="0.0"/> <GradientStop Color="#FFFFFF00" Offset="0.1"/> <GradientStop Color="#FF00FF00" Offset="0.2"/> <GradientStop Color="#FF00FFFF" Offset="0.3"/> <GradientStop Color="#FF0000FF" Offset="0.4"/> <GradientStop Color="#FFFF00FF" Offset="0.5"/> <GradientStop Color="#FFFF0000" Offset="0.6"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush>
, – , , . MVVM: . , , , .
!
PS . , , , , . .
List _toches . , _toches
Canvas.Children
, _toches
.
: Polyline OpacityMask Canvas. , .
(). , .
, , . , , .
<LinearGradientBrush x:Key="RainbowBrush" StartPoint="0 0" EndPoint="0 0.6"> <LinearGradientBrush.Transform> <ScaleTransform ScaleY="2.8"/> </LinearGradientBrush.Transform> <LinearGradientBrush.GradientStops> <GradientStop Color="#FFFF0000" Offset="0.0"/> <GradientStop Color="#FFFFFF00" Offset="0.1"/> <GradientStop Color="#FF00FF00" Offset="0.2"/> <GradientStop Color="#FF00FFFF" Offset="0.3"/> <GradientStop Color="#FF0000FF" Offset="0.4"/> <GradientStop Color="#FFFF00FF" Offset="0.5"/> <GradientStop Color="#FFFF0000" Offset="0.6"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush>
, – , , . MVVM: . , , , .
!
PS . , , , , . .
List _toches . , _toches
Canvas.Children
, _toches
.
: Polyline OpacityMask Canvas. , .
(). , .
, , . , , .
<LinearGradientBrush x:Key="RainbowBrush" StartPoint="0 0" EndPoint="0 0.6"> <LinearGradientBrush.Transform> <ScaleTransform ScaleY="2.8"/> </LinearGradientBrush.Transform> <LinearGradientBrush.GradientStops> <GradientStop Color="#FFFF0000" Offset="0.0"/> <GradientStop Color="#FFFFFF00" Offset="0.1"/> <GradientStop Color="#FF00FF00" Offset="0.2"/> <GradientStop Color="#FF00FFFF" Offset="0.3"/> <GradientStop Color="#FF0000FF" Offset="0.4"/> <GradientStop Color="#FFFF00FF" Offset="0.5"/> <GradientStop Color="#FFFF0000" Offset="0.6"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush>
, – , , . MVVM: . , , , .
!
PS . , , , , . .
List _toches . , _toches
Canvas.Children
, _toches
.
: Polyline OpacityMask Canvas. , .
(). , .
, , . , , .
<LinearGradientBrush x:Key="RainbowBrush" StartPoint="0 0" EndPoint="0 0.6"> <LinearGradientBrush.Transform> <ScaleTransform ScaleY="2.8"/> </LinearGradientBrush.Transform> <LinearGradientBrush.GradientStops> <GradientStop Color="#FFFF0000" Offset="0.0"/> <GradientStop Color="#FFFFFF00" Offset="0.1"/> <GradientStop Color="#FF00FF00" Offset="0.2"/> <GradientStop Color="#FF00FFFF" Offset="0.3"/> <GradientStop Color="#FF0000FF" Offset="0.4"/> <GradientStop Color="#FFFF00FF" Offset="0.5"/> <GradientStop Color="#FFFF0000" Offset="0.6"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush>
, – , , . MVVM: . , , , .
!
PS . , , , , . .