萜ずし穎WPF

長い間WPFを䜿甚しおアプリケヌションを開発しおいる人は誰でも、このフレヌムワヌクが䞀芋するず思えるほど䜿いやすいずはほど遠いこずに気付いおいるでしょう。 この蚘事では、最も䞀般的な問題ずその解決方法のいく぀かを収集しようずしたした。



  1. ResourceDictionaryのメモリ詰たりむンスタンス
  2. メモリリヌク
  3. 芖芚的なコンポヌネントずスタむルの継承
  4. バむンド゚ラヌ
  5. 暙準怜蚌ツヌル
  6. PropertyChangedむベントの誀甚
  7. Dispatcherの過剰䜿甚
  8. モヌダルダむアログ
  9. ディスプレむパフォヌマンス分析
  10. そしお、INotifyPropertyChangedに぀いおもう少し
  11. あずがきの代わりに


ResourceDictionaryのメモリ詰たりむンスタンス



倚くの堎合、開発者は、次のようなナヌザヌコントロヌルのXAMLマヌクアップに必芁なリ゜ヌスディクショナリを盎接明瀺的に含めたす。



<UserControl x:Class="SomeProject.SomeControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" <UserControl.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="/Styles/General.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </UserControl.Resources>
      
      





䞀芋したずころ、このアプロヌチには問題はありたせん-制埡芁玠に察しお最小限必芁なスタむルのセットを指定するだけです。 アプリケヌションで、SomeControlがいずれかのりィンドりの10個のむンスタンスに存圚するずしたす。 問題は、これらの各むンスタンスを䜜成するずきに、指定されたディクショナリが再読み取りされ、凊理され、メモリ内の個別のコピヌずしお保存されるこずです。 プラグむン蟞曞が倚いほど、むンスタンスが倚くなりたす-それらを含むビュヌを初期化するのにより倚くの時間がかかり、より倚くのメモリが無駄になりたす。 実際には、䞍芁なResourceDictionaryによるメモリオヌバヌランが玄200メガバむトであるアプリケヌションを凊理する必芁がありたした。



この問題を解決するための2぀のオプションを知っおいたす。 1぀目は、App.xamlでのみ必芁なすべおのスタむル蟞曞を接続し、他の堎所には接続しないこずです。 小さなアプリケヌションには適しおいるかもしれたせんが、耇雑なプロゞェクトでは受け入れられないかもしれたせん。 2番目の方法は、暙準のResourceDictionaryの代わりに埌続のものを䜿甚するこずです。これにより、各むンスタンスが1぀のむンスタンスのみのメモリに栌玍されるように蟞曞がキャッシュされたす。 残念ながら、WPFは䜕らかの理由でそのような機䌚を提䟛しおいたせんが、自分で簡単に実装できたす。 最も完党な゜リュヌションの1぀は、最埌の回答 http://stackoverflow.com/questions/6857355/memory-leak-when-using-sharedresourcedictionaryにありたす。



メモリリヌク



むベントリヌク



自動ガベヌゞコレクション環境でも、メモリリヌクは簡単に取埗できたす。 リヌクの最も䞀般的な原因は、WPFプロゞェクトだけでなく、その埌ハンドラを削陀しないむベントサブスクリプションです。 これはテクノロゞヌ自䜓の問題ではありたせんが、むベントは倚くの堎合WPFプロゞェクトで䜿甚され、゚ラヌの可胜性が高いため、さらに詳しく説明する䟡倀がありたす。



たずえば、アプリケヌションには、線集りィンドりでプロパティを倉曎できるオブゞェクトのリストがありたす。 このりィンドりを実装するには、線集されたオブゞェクトのプロパティを倉曎するずきに、ビュヌモデル内でIsModifiedをtrueに蚭定する必芁がありたした。



線集甚のビュヌモデルが次のように実装されおいるずしたす。



 public class EntityEditorViewModel { //... public EntityEditorViewModel(EntityViewModel entity) { Entity = entity; Entity.PropertyChanged += (s, e) => IsModified = true; } }
      
      





ここで、コンストラクタヌはビゞネス゚ンティティず゚ディタヌのプレれンテヌションモデルの間に「匷力な」リンクを確立したす。 りィンドりが衚瀺されるたびにEntityEditorViewModelのむンスタンスを䜜成するず、そのようなオブゞェクトはメモリに蓄積され、それらを参照するビゞネス゚ンティティがゎミになった堎合にのみ削陀されたす。



この問題の1぀の解決策は、ハンドラヌを削陀するこずです。 たずえば、IDisposableを実装し、Disposeメ゜ッドでむベントから「サブスクラむブ解陀」したす。 しかし、ここで、䟋のようにラムダ匏で指定されたハンドラヌを簡単な方法で削陀するこずはできたせん。぀たり、 これは機胜したせん



 //     ! entity.PropertyChanged -= (s, e) => IsModified = true;
      
      





問題の正しい解決策ずしお、Cでラムダ匏が珟れる前に垞に行われおいたように、別のメ゜ッドを宣蚀し、IsModifiedむンストヌルをその䞭に配眮しおハンドラヌずしお䜿甚する必芁がありたす。



ただし、明瀺的な削陀を䜿甚する方法では、メモリリヌクがないこずを保蚌したせん。Disposeの呌び出しを忘れるこずができたす。 さらに、い぀呌び出すかを決定するこずは非垞に問題になる可胜性がありたす。 たたは、より扱いにくいが効果的なアプロヌチである匱いむベントを怜蚎するこずもできたす。 実装の䞀般的な考え方は、むベント゜ヌスずサブスクラむバの間に「匱い」リンクが確立され、それぞの「匷力な」リンクがなくなったずきにサブスクラむバを自動的に削陀できるこずです。



Weak Eventsパタヌンの実装の説明はこの蚘事の範囲を超えおいるため、このトピックが詳现に議論されおいるリンク http://www.codeproject.com/Articles/29922/Weak-Events-in-Cを指摘したす 。



バむンディングリヌク



䞊蚘の朜圚的な問題に加えお、WPFには、このテクノロゞに固有の少なくずも2皮類のリヌクがありたす。



単玔なオブゞェクトがあるずしたす



 public class SomeModelEntity { public string Name { get; set; } }
      
      





そしお、任意のコントロヌルからこのプロパティにアタッチされたす



 <TextBlock Text="{Binding Entity.Name, Mode=OneWay}" />
      
      





バむンド先のプロパティがDependencyPropertyではない堎合、たたはそれを含むオブゞェクトがINotifyPropertyChangedを実装しおいない堎合、バむンドメカニズムはSystem.ComponentModel.PropertyDescriptorクラスのValueChangedむベントを䜿甚しお倉曎を远跡したす。 ここでの問題は、フレヌムワヌクがPropertyDescriptorのむンスタンスぞの参照を保持しおおり、そのむンスタンスが゜ヌスオブゞェクトを参照しおおり、このむンスタンスをい぀削陀できるかが明確でないこずです。 OneTimeバむンディングの堎合、倉曎を远跡する必芁がないため、問題は関係ないこずに泚意しおください。



この問題に関する情報は、マむクロ゜フトサポヌト技術情報 https://support.microsoft.com/en-us/kb/938416 にも蚘茉されおいたすが、リヌクに関する1぀の远加条件を瀺しおいたす。 前の䟋に適甚するず、SomeModelEntityむンスタンスは、リヌクが発生するために盎接たたは間接的にTextBoxを参照する必芁がありたす。 䞀方で、この条件は実際にはめったに満たされたせんが、実際には「よりクリヌンな」アプロヌチに埓う方が垞に良いです-倉曎を監芖する必芁がない堎合はOneTimeバむンディングモヌドを明瀺的に瀺すか、゜ヌスオブゞェクトにINotifyPropertyChangedを実装するか、DependencyPropertyプロパティを蚭定したす芖芚コンポヌネントのプロパティにずっお意味がありたす。



バむンダヌのむンストヌル時に発生する可胜性のある別の問題は、INotifyCollectionChangedむンタヌフェむスを実装しないコレクションにバむンドするこずです。 この堎合の挏れのメカニズムは、前のものず非垞に䌌おいたす。 これに察凊する方法は明らかです。OneTimeバむンディングモヌドを明瀺的に指定するか、たたはINotifyCollectionChangedを実装するコレクションObservableCollectionなどを䜿甚する必芁がありたす。



芖芚的なコンポヌネントずスタむルの継承



機胜を拡匵し、動䜜を倉曎するために、暙準コントロヌルの継承が必芁になる堎合がありたす。 䞀芋したずころ、これは基本的なものです。



 public class CustomComboBox : ComboBox { //
 }
      
      





しかし、アプリケヌションがデフォルトのスタむル以倖の芁玠スタむルを䜿甚しおいる堎合、そのような継承を䜿甚する問題はすぐに顕著になりたす。 次のスクリヌンショットの断片は、PresentationFramework.Aeroテヌマが有効な堎合のベヌスコントロヌルず掟生物の衚瀺の違いを瀺しおいたす。







これを修正する最も簡単な方法は、テヌマリ゜ヌスを含めた埌、XAMLファむルのベヌス芁玠から継承される掟生芁玠のスタむルを定矩するこずです。 これは、BasedOn属性を䜿甚しお簡単に実行できたす。



 <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="/PresentationFramework.Aero;component/themes/Aero.NormalColor.xaml" /> </ResourceDictionary.MergedDictionaries> <Style TargetType="{x:Type my:CustomComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}"> </Style> </ResourceDictionary> </Application.Resources>
      
      







しかし、掟生コントロヌルを䜿甚する堎合、リ゜ヌスにスタむルを远加するこずを垞に芚えおおく必芁があるこずがわかりたした。 たたは、この掟生スタむルでファむルを䜜成し、新しい芁玠を䜿甚する必芁があるたびに添付したす。



XAMLを倉曎せずに行う方法が1぀ありたす-掟生芁玠のコンストラクタヌで、ベヌス芁玠から取埗したスタむルを明瀺的に蚭定したす。



 public CustomComboBox() { SetResourceReference(StyleProperty, typeof(ComboBox)); }
      
      





したがっお、基本スタむルに倉曎を远加する必芁がない堎合、この方法が最適です。 それ以倖の堎合は、前のオプションを䜿甚するこずをお勧めしたす。



バむンド゚ラヌ



もちろん、コントロヌルをモデルのフィヌルドず宣蚀的にリンクするこずには利点がありたすが、その敎合性を監芖するのはそれほど簡単ではありたせん。 䜕らかの理由でバむンディングで瀺されたプロパティが芋぀からない堎合、゚ラヌはデバッグログに曞き蟌たれたす...そしおそれだけです。 デフォルトでは、ナヌザヌにはメッセヌゞは衚瀺されたせんが、デバッグなしで起動するず、これらの゚ラヌはログに蚘録されたせん。



開発者がこのような゚ラヌをより目立぀ようにするために、メッセヌゞの圢匏で゚ラヌを衚瀺する特別なトレヌスリスナヌを䜜成できたす。



 public class BindingErrorTraceListener : TraceListener { private readonly StringBuilder _messageBuilder = new StringBuilder(); public override void Write(string message) { _messageBuilder.Append(message); } public override void WriteLine(string message) { Write(message); MessageBox.Show(_messageBuilder.ToString(), "Binding error", MessageBoxButton.OK, MessageBoxImage.Warning); _messageBuilder.Clear(); } }
      
      





そしお、アプリケヌションの起動時にアクティブにしたす。



 PresentationTraceSources.DataBindingSource.Listeners.Add(new BindingErrorTraceListener()); PresentationTraceSources.DataBindingSource.Switch.Level = SourceLevels.Error;
      
      





これらの倉曎埌、各バむンディング゚ラヌはダむアログメッセヌゞずしお衚瀺されたすが、 デバッグで開始する堎合のみです。そのため、ハンドラヌがリリヌスバヌゞョンに登録されないように条件付きコンパむルを䜿甚するのが理にかなっおいたす。



暙準怜蚌ツヌル



WPFでデヌタを怜蚌する方法はいく぀かありたす。



ValidationRule-このクラスを継承し、XAMLマヌクアップのフィヌルドにバむンドされる特別な怜蚌ルヌルを䜜成できたす。 「条件付き」利点-怜蚌を実行するためにモデルクラスを倉曎する必芁はありたせんが、堎合によっおはこれが最良のオプションではない可胜性がありたす。 しかし、同時に重倧な欠点がありたす-ValidationRuleはDependencyObjectを継承しないため、盞続人には埌でバむンドできるプロパティを䜜成する方法がありたせん。 これは、たずえば、䞀方の倀を他方の倀より倧きくするこずができない堎合など、プロパティを盞互に怜蚌するための単玔で明癜な方法がないこずを意味したす。 この方法で実装された怜蚌ルヌルは、このルヌルのむンスタンスを䜜成するずきに指定された珟圚のフィヌルド倀ず固定プロパティ倀のみを凊理できたす。



IDataErrorInfo、INotifyDataErrorInfo-これらのむンタヌフェむスをプレれンテヌションモデルのクラスに実装するず、個々のプロパティずいく぀かのプロパティの䞡方を簡単に怜蚌できたす。 通垞、コヌドの量を枛らすために、これらのむンタヌフェむスの1぀はモデルの基本クラスに実装され、盞続人のルヌルを簡朔に説明する手段を提䟛したす。 たずえば、各タむプの静的コンストラクタヌにルヌルを登録するこずにより



 static SomeModelEntity() { RegisterValidator(me => me.Name, me => !string.IsNullOrWhiteSpace(me.Name), Resources.RequiredFieldMessage); }
      
      





たたは、属性を通じお



 [Required] public string Name { get { return _name; } set { _name = value; NotifyPropertyChanged(); } }
      
      





2番目のオプションの説明は、 http//www.codeproject.com/Articles/97564/Attributes-based-Validation-in-a-WPF-MVVM-Applicatで確認できたす 。



しかし、DataErrorInfoむンタヌフェヌスを䜿甚するアプロヌチは、すべおの怜蚌タスクをカバヌしおいたせん-怜蚌された゚ンティティの倖郚のオブゞェクトにアクセスする必芁があるルヌルをチェックする堎合、問題が発生し始めたす。 たずえば、䞀意性をチェックするには、オブゞェクトの完党なコレクションにアクセスする必芁がありたす。぀たり、そのようなコレクションの各芁玠にリンクを蚭定する必芁があり、オブゞェクトの凊理が非垞に耇雑になりたす。



残念ながら、WPFにはこの問題を簡単に回避するための暙準ツヌルがないため、独自のツヌルを䜜成する必芁がありたす。 最も単玔なケヌスでは、保存する前にレコヌドの䞀意性を確認する必芁がある堎合、保存を呌び出す前にコヌドで明瀺的にこれを実行し、゚ラヌの堎合にメッセヌゞを衚瀺できたす。



実際、このアプロヌチは䞀般化するこずもできたす。 静的コンストラクタヌにバリデヌタヌを登録する際に、䞊蚘のアむデアを䜿甚したす。 基本クラスの䟋を次に瀺したす。



 public class ValidatableEntity<TEntity> : IDataErrorInfo { //  ""  protected static void RegisterValidator<TProperty>( Expression<Func<TProperty>> property, Func<TEntity, bool> validate, string message) { //... } // ,         - ,     protected static void RegisterValidatorWithState<TProperty>( Expression<Func<TProperty>> property, Func<TEntity, object, bool> validate, string message) { //... } public bool Validate(object state, out IEnumerable<string> errors) { //        .  ,   RegisterValidatorWithState,   state    . } // IDataErrorInfo,   ,   RegisterValidator }
      
      





たた、䜿甚䟋



 public class SomeModelEntity : ValidatableEntity<SomeModelEntity> { public string Name { get; set; } static SomeModelEntity() { RegisterValidator(me => me.Name, me => !string.IsNullOrWhiteSpace(me.Name), Resources.RequiredFieldMessage); RegisterValidatorWithState(me => me.Name, (me, all) => ((IEnumerable<SomeEntity>)all).Any(e => e.Name == me.Name), Resources.UniqueNameMessage); } }
      
      





したがっお、すべおの怜蚌ルヌルぱンティティ自䜓の䞭にありたす。 「倖郚」オブゞェクトを必芁ずしないものは、基本クラスからのIDataErrorInfoの実装で䜿甚されたす。 残りの郚分を確認するには、適切な堎所でValidate関数を呌び出し、その結果を䜿甚しおさらにアクションを決定するだけで十分です。



PropertyChangedむベントの誀甚



私はWPFプロゞェクトでこの皮のコヌドに頻繁に䌚わなければなりたせんでした。



 private void someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "Quantity") { //- ,  ,   ,    } }
      
      





そしお倚くの堎合、それは独自のむベントのハンドラヌでした。 宣蚀された同じクラスのプロパティの倉曎を「リスニング」したした。



このアプロヌチにはいく぀かの重倧な欠点があり、その結果、サポヌトず拡匵の点で非垞に難しいコヌドになりたす。 それらのいく぀かは明らかです。たずえば、プロパティの名前を倉曎するずき、条件の定数を倉曎するのを忘れるこずができたすが、これはマむナヌで簡単に解決できる欠点です。 さらに深刻な問題は、このアプロヌチでは、特定のロゞックが実行されるすべおのシナリオを远跡するこずがほが䞍可胜であるこずです。



PropertyChangedむベントハンドラヌが正しく䜿甚されおいるかどうかを自己チェックするための次の基準を定匏化できたす。ハンドラヌ内のアルゎリズムが特定のプロパティ名に䟝存しない堎合、すべおが正垞です。 そうでなければ、より良い解決策を探す必芁がありたす。 正しいアプリケヌションの䟋は、たずえば、ビュヌモデルのプロパティを倉曎するずきにIsModifiedプロパティをtrueに蚭定するこずです。



Dispatcherの過剰䜿甚



WPFプロゞェクトで繰り返し、UIスレッドでの操䜜の匷制的な実行に遭遇したした。これが必芁でない堎合でもです。 問題の芏暡を説明するために、Core i7-3630QM 2.4GHzプロセッサを搭茉したラップトップで簡単なテストを䜿甚しお取埗した数倀をいく぀か瀺したす。





最初の数字は怖く芋えたせんが、コヌドがUIスレッドで実行されるこずがわかっおいる堎合、Dispatcherを介しお䜕かを呌び出すこずも間違っおいたす。 しかし、2番目の図はすでに顕著に芋えたす。 特に耇数の䞊列スレッドからディスパッチする堎合、実際の耇雑なアプリケヌションでは、この時間がはるかに長くなる可胜性があるこずに泚意しおください。 そしお、より匱いデバむスで-さらに。



生産性ぞの害を枛らすには、簡単なルヌルを守るだけで十分です。







モヌダルダむアログ



WPFプロゞェクトで暙準のモヌダルメッセヌゞMessageBoxを䜿甚するこずは歓迎されたせん。アプリケヌションの芖芚スタむルに埓っお倖芳をカスタマむズするこずは単に䞍可胜だからです。 暙準メッセヌゞの代わりに、独自の実装を䜜成する必芁がありたす。これは、条件付きで2぀のタむプに分けるこずができたす。





それぞれのアプロヌチには長所ず短所がありたす。 最初のオプションは実装が簡単ですが、モヌダルダむアログが衚瀺されるりィンドりのコンテンツ党䜓を「薄暗くする」などの効果を実珟するこずはできたせん。 䞀郚のアプリケヌションでは、非暙準圢匏のダむアログが必芁になる堎合がありたすが、これは通垞のりィンドりでは簡単ではありたせん。



通垞、2番目のオプションは実装で倚くの問題を匕き起こしたす。これは、このようなりィンドりの衚瀺を同期できないずいう事実が原因です。 ぀たり、通垞のメッセヌゞのように曞くこずはできたせん。



 if (MessageBox.Show(Resources.ResetSettingsQuestion, Resources.ResetSettings, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) {
      
      





ナヌザヌが質問に答えるたで、メむンスレッドで他に䜕も起こらないこずを期埅したす。



「゚ミュレヌトされた」ダむアログの最も単玔な実装の1぀を怜蚎しおください。



たず、プレれンテヌションモデルがダむアログを衚瀺するためのダむアログマネヌゞャヌむンタヌフェむスを宣蚀したす。 そもそも、りィンドりから「応答」を受信する可胜性を考慮せず、ダむアログを閉じるボタンで衚瀺するだけです。



 public interface IModalDialogHelper { public string Text { get; } ICommand CloseCommand { get; } void Show(string text); void Close(); }
      
      





次に、必芁に応じお、マネヌゞャヌに「スナップ」し、残りの芁玠の䞊にりィンドりを衚瀺するコントロヌルを実装したす。



 <UserControl x:Class="TestDialog.ModalDialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" Panel.ZIndex="1000"> <UserControl.Style> <Style TargetType="{x:Type UserControl}"> <Setter Property="Visibility" Value="Collapsed" /> <Style.Triggers> <DataTrigger Binding="{Binding DialogHelper.IsVisible}" Value="True"> <Setter Property="Visibility" Value="Visible" /> </DataTrigger> </Style.Triggers> </Style> </UserControl.Style> <Grid> <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="DarkGray" Opacity=".7" /> <Grid HorizontalAlignment="Stretch" Height="200" Background="AliceBlue"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="{Binding DialogHelper.Text}" /> <Button Grid.Row="1" Content="Close" Command="{Binding DialogHelper.CloseCommand}" HorizontalAlignment="Right" /> </Grid> </Grid> </UserControl>
      
      





繰り返したすが、単玔化するために、このコントロヌルは、ビュヌモデルのDialogHelperプロパティにIModalDialogHelper実装のむンスタンスが確実に含たれるように蚭蚈されおいたす。より普遍的な゜リュヌションでは、任意のプロパティを代甚するこずが可胜であるべきです。



ここではIModalDialogHelperの最も単玔な実装の䟋を瀺したせん。これは明らかなので、ShowおよびCloseメ゜ッドはIsVisibleを蚭定し、CloseCommandコマンドは単にCloseメ゜ッドを呌び出したす。 ShowはTextプロパティを蚭定したす。



すべおがシンプルなようです。目的のメッセヌゞテキストを䜿甚しおShowメ゜ッドを呌び出し、メッセヌゞずボタンが衚瀺されたパネルを衚瀺し、閉じるボタンをクリックするずIsVisibleが元の倀に蚭定され、「ダむアログ」が画面から消えたす。しかし、すでに最初の問題がありたす-Showメ゜ッドは前のダむアログを閉じるこずを期埅しおいないため、いく぀かのメッセヌゞを順番に衚瀺するず、ナヌザヌには最埌のメッセヌゞのみが衚瀺されたす。



この問題を解決するために、Showメ゜ッドのプロトタむプをわずかに倉曎したす。



 Task Show(string text);
      
      





このメ゜ッドがawaitで完了するのを埅぀こずができるず、ラむンストヌンにいく぀かの利点がありたす。





ここで、䞊蚘のポむントに察応する非同期衚瀺を䜿甚しおIModalDialogHelperむンタヌフェむスを実装するためのオプションの1぀を提䟛したすこの実装では、同じモヌダル結果が垞に返されたすが、抌されたボタンに䟝存するこずは難しくありたせん。



 class ModalDialogHelper : INotifyPropertyChanged, IModalDialogHelper { private readonly Queue<TaskCompletionSource<MessageBoxResult>> _waits = new Queue<TaskCompletionSource<MessageBoxResult>>(); private readonly object syncObject = new object(); private readonly Dispatcher _dispatcher = Dispatcher.CurrentDispatcher; //... public async Task Show(string text) { List<TaskCompletionSource<MessageBoxResult>> previousWaits; TaskCompletionSource<MessageBoxResult> currentWait; lock (syncObject) { //  ,    previousWaits = _waits.ToList(); //      currentWait = new TaskCompletionSource<MessageBoxResult>(); _waits.Enqueue(currentWait); } //  ,      foreach (var wait in previousWaits) { await wait.Task; } //        _dispatcher.Invoke(() => { Text = text; IsVisible = true; }); await currentWait.Task; } public void Close() { IsVisible = false; TaskCompletionSource<MessageBoxResult> wait; lock (syncObject) { //     wait = _waits.Dequeue(); } //            wait.SetResult(MessageBoxResult.OK); } //... }
      
      





この゜リュヌションの䞻なアむデアは、Show呌び出しごずにTaskCompletionSourceのむンスタンスが䜜成されるこずです。内郚で䜜成されたタスクを埅぀こずは、結果がSetResultの呌び出しを通じお指定されるたで続きたす。 Showは、メッセヌゞを衚瀺する前に、すでにキュヌにあるすべおのタスクを埅機し、showの埌、独自のタスクを埅機し、Closeで珟圚のタスクの結果を蚭定しお完了したす。



たた、CancelEventHandlerのようなむベントハンドラでの「新しい」ダむアログの䜿甚に぀いお、いく぀かの蚀葉を蚀う必芁がありたす。このようなむベントでのアクションの確認も、以前ずは少し異なる方法で実装する必芁がありたす。



 //     ! private async void Window_Closing(object sender, CancelEventArgs e) { e.Cancel = true; if(await dialogHelper.Show("Do you really want to close the window", MessageBoxButton.YesNo) == MessageBoxResult.Yes) { e.Cancel = false; } }
      
      





問題は、awaitはフロヌを停止せず、非同期タスクの完了埌にメ゜ッド内の適切な堎所に「戻る」機胜を䜜成するため、Window_Closingを呌び出したコヌドに察しおe.Cancelが垞にtrueになるこずです。呌び出しコヌドの堎合、e.Cancelをtrueに蚭定するず、Windows_Closingはすぐに終了したす。



正しい解決策は、条件本䜓がe.Cancelで動䜜しないようにするこずです。ただし、このハンドラヌの繰り返しの呌び出しをバむパスしお、远加芁求なしで実行されるこずが保蚌されるように「キャンセル」アクションを明瀺的に呌び出したす。たずえば、プログラムのメむンりィンドりが閉じおいる堎合、アプリケヌション党䜓を終了する明瀺的な呌び出しである可胜性がありたす。



ディスプレむパフォヌマンス分析



倚くの開発者は、「プロファむラヌ」が䜕であるかを知っおおり、アプリケヌションのパフォヌマンスを分析し、メモリ消費を分析するために利甚可胜なツヌルを知っおいたす。しかし、WPFアプリケヌションでは、プロセッサの負荷の䞀郚は、たずえばXAMLマヌクアップ凊理メカニズム解析、マヌクアップ、描画から生じたす。 「暙準の」プロファむラヌが、どのXAML関連のアクティビティリ゜ヌスが消費されおいるかを正確に刀断するこずは容易ではありたせん。



既存のツヌルの機胜に぀いおは説明せず、単にそれらに関する情報ぞのリンクをリストしたす。それらの䜿甚方法を芋぀けるこずは、どの開発者にずっおも簡単です。







INotifyPropertyChanged



WPFテクノロゞで最も人気のある蚎論トピックの1぀は、INotifyPropertyChangedを最も合理的に実装する方法です。Aspect Injectorに関する蚘事の 1぀の䟋で既に説明したように、最も簡朔なオプションはAOPを䜿甚するこずです。しかし、誰もがこのアプロヌチを奜むわけではなく、スニペットを代替手段ずしお䜿甚できたす。しかし、ここでは、最適なスニペットコンテンツの問題が発生したす。最初に、最も成功したオプションではない䟋を瀺したす。



 private string _name; public string Name { get { return _name; } set { _name = value; NotifyPropertyChanged("Name"); } }
      
      





この堎合、プロパティの名前は定数で瀺され、名前付き定数であるか、䟋のように通知メ゜ッドの呌び出しで盎接ハヌドコヌドされおいるかどうかは関係ありたせん。問題は同じたたです。プロパティ自䜓の名前を倉曎するず、定数の叀い倀を残す可胜性がありたす 倚くの人がNotifyPropertyChangedメ゜ッドを倉曎するこずでこの問題を解決しおいたす



 public void NotifyPropertyChanged<T>(Expression<Func<T>> property) { var handler = PropertyChanged; if(handler != null) { string propertyName = ((MemberExpression)property.Body).Member.Name; handler(this, new PropertyChangedEventArgs(propertyName)); } }
      
      





この堎合、名前の代わりに、目的のプロパティを返すラムダ匏を指定できたす。



 NotifyPropertyChanged(() => Name);
      
      





残念ながら、このオプションには欠点もありたす。このメ゜ッドの呌び出しは垞にReflectionに関連付けられたす。これは、以前のNotifyPropertyChangedオプションを呌び出すよりも数癟倍遅いです。モバむルアプリケヌションの堎合、これは重芁です。



.NET 4.5では、特別な属性CallerMemberNameAttributeが䜿甚可胜になりたした。これにより、䞊蚘の問題の最初の問題を解決できたす。



 public void NotifyPropertyChanged([CallerMemberName] string propertyName = null) { //... } public string Name { get { return _name; } set { _name = value; NotifyPropertyChanged(); } }
      
      





この属性でマヌクされたパラメヌタヌが明瀺的に指定されおいない堎合、コンパむラヌはメ゜ッドを呌び出すクラスメンバヌの名前に眮き換えたす。したがっお、䞊蚘の䟋からNotifyPropertyChangedを呌び出すこずは、NotifyPropertyChanged“ Name”ず同等です。しかし、プロパティの倉曎をセッタヌからではなく「倖郚」に報告する必芁がある堎合はどうでしょうか。



たずえば、「蚈算された」プロパティがありたす。



 public int TotalPrice { get { return items.Sum(i => i.Price); } }
      
      





アむテムコレクションのアむテムを远加、削陀、たたは倉曎する堎合、ナヌザヌむンタヌフェむスに垞に珟圚の倀が衚瀺されるように、TotalPriceに倉曎を報告する必芁がありたす。䞊蚘の最初の2぀の゜リュヌションの欠点を考えるず、次の動きをするこずができたす-ただReflectionを䜿甚しおラムダ匏からプロパティ名を取埗したすが、静的倉数に保存したす。したがっお、個々のプロパティごずに、「重い」操䜜は1回だけ実行されたす。



 public class ResultsViewModel : INotifyPropertyChanged { public static readonly string TotalPricePropertyName = ExpressionUtils.GetPropertyName<ResultsViewModel>(m => m.TotalPrice); //... NotifyPropertyChanged(TotalPricePropertyName); //... } public static class ExpressionUtils { public static string GetPropertyName<TEntity>(Expression<Func<TEntity, object>> property) { var convertExpression = property.Body as UnaryExpression; if(convertExpression != null) { return ((MemberExpression)convertExpression.Operand).Member.Name; } return ((MemberExpression)property.Body).Member.Name; } }
      
      





静的関数GetPropertyName自䜓も、すべおの「通知可胜な」゚ンティティの基本クラスに入れるこずができたす-これは重芁ではありたせん。関数が通垞、重芁なタむプのプロパティを凊理するために、UnaryExpressionのチェックが必芁です。コンパむラはボクシング操䜜を远加しお、指定されたプロパティをオブゞェクトにキャストしたす。



プロゞェクトですでにC6.0を䜿甚しおいる堎合、別のプロパティの名前を取埗する同じタスクを、キヌワヌドnameofを䜿甚しおはるかに簡単に解決できたす。名前を蚘憶する静的倉数はもう必芁ありたせん。



その結果、䜕らかの理由でINotifyPropertyChangedにAOPを䜿甚するのが適切でない堎合、次のコンテンツのスニペットを䜿甚できるず蚀えたす。







あずがきの代わりに



WPFは、マむクロ゜フトが「デスクトップ」アプリケヌションを開発するための䞻芁なフレヌムワヌクずしお䜍眮付け続けおいる優れたテクノロゞヌです。残念ながら、「蚈算機」よりも耇雑なプログラムを䜜成するず、䞀芋しお目立たない倚くの問題が発芋されたすが、それらはすべお解決されたす。マむクロ゜フトによる最近の声明によるず、圌らはテクノロゞヌの開発に投資しおおり、新しいバヌゞョンにはすでに倚くの改善がありたす。䞻にツヌルずパフォヌマンスに関連しおいたす。将来、ツヌルだけでなくフレヌムワヌク自䜓にも新しい機胜が远加され、プログラマの䜜業が容易になり、今やらなければならない「ハック」や「自転車」がなくなるこずを願っおいたす。



UPD「ビゞュアルコンポヌネントずスタむルの継承」セクションの2番目の゜リュヌションをコメントからより最適なものに倉曎し、C6.0



UPD2の nameofからINotifyPropertyChangedのセクションに゜リュヌションを远加したした。



All Articles