子画面を閉じたとき、親画面で決まった処理を行いたい
子画面のClosedイベントにやりたい処理を登録したデリゲートを登録
でもよいんだけど、これだと毎回子画面追加(インスタンス)時にこのデリゲートも一緒に登録してあげないといけない。毎回。
なんかだるいなーおんなじ処理なのになーって思って
OwnedWindowsのChangedEventとかないのかなーとか調べたけど なさそう。。。
だから、自作イベントループ作ってOwnedWindowsの値監視して減少したら発火~なイベント用意。
private void CloseSubWindowEventloop() { int owNum = 1; while (true) { this.Dispatcher.Invoke(new Action(() => { if (owNum > this.OwnedWindows.Count) { this.RefreshListViewEventHandler(); } owNum = this.OwnedWindows.Count; })); // SleepはDispatcher外に置くのがポイント。Dispatcher.Invokeが開きっぱなしだと、メインスレッドに戻れる隙ができず、GUIが固まる。 System.Threading.Thread.Sleep(300); } }
かなり簡潔にかけて気持ちいい!
。。。けど、こんな気軽にワーカースレッド使ってよいものなのだろうか。。。
Prism.CommandsのDelegateCommand<T>で、VisualStudioのデザイナーが例外を吐く
また躓いたからメモ
現象
Windowクラス(を継承した独自のMyWindowクラス)内のButtonコントロールのCommandに、ViewModel側で用意したDelegateCommand
CommandParameterのバインドで、ElementName引数に渡したいWindowのElementNameを指定してバインドを試みるもVisualStudioのデザイナーがインスタンスを生成するときに例外を吐き、デザイナーが表示されない問題に遭遇した。
やりたかったこと
コマンドを実施するボタンが所属するウィンドウオブジェクト自体をCommandParameterとして渡したい。
背景
ボタンを処理内で、ボタンが所属するWindowクラス(を継承した独自のMyWindowクラス)をクローズしたかった。
例外がスローされました。
InvalidCastException: 型 'Microsoft.VisualStudio.DesignTools.WpfDesigner.InstanceBuilders.WindowInstance' のオブジェクトを型 'testNamespace.MyWindow にキャストできません。
StackTrace
場所 Prism.Commands.DelegateCommand`1.<>c__DisplayClass1_0.<.ctor>b__1(Object o)
場所 Prism.Commands.DelegateCommandBase.CanExecute(Object parameter)
場所 Prism.Commands.DelegateCommandBase.System.Windows.Input.ICommand.CanExecute(Object parameter)
場所 MS.Internal.Commands.CommandHelpers.CanExecuteCommandSource(ICommandSource commandSource)
場所 System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute()
場所 System.Windows.Controls.Primitives.ButtonBase.HookCommand(ICommand command)
場所 System.Windows.Controls.Primitives.ButtonBase.OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
場所 System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
場所 System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
場所 System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
場所 System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
場所 System.Windows.DependencyObject.InvalidateProperty(DependencyProperty dp, Boolean preserveCurrentValue)
場所 System.Windows.Data.BindingExpressionBase.Invalidate(Boolean isASubPropertyChange)
場所 System.Windows.Data.BindingExpression.TransferValue(Object newValue, Boolean isASubPropertyChange)
場所 System.Windows.Data.BindingExpression.Activate(Object item)
場所 System.Windows.Data.BindingExpression.AttachToContext(AttachAttempt attempt)
場所 System.Windows.Data.BindingExpression.MS.Internal.Data.IDataBindEngineClient.AttachToContext(Boolean lastChance)
場所 MS.Internal.Data.DataBindEngine.Task.Run(Boolean lastChance)
場所 MS.Internal.Data.DataBindEngine.Run(Object arg)
場所 MS.Internal.Data.DataBindEngine.OnLayoutUpdated(Object sender, EventArgs e)
場所 System.Windows.ContextLayoutManager.fireLayoutUpdateEvent()
場所 System.Windows.ContextLayoutManager.UpdateLayout()
場所 System.Windows.UIElement.UpdateLayout()
<Window x:Class="testNamespace.MyWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:testNamespace" x:Name="myWindow" Height="350" Width="500"> <Grid> <Button x:Name="button_Output" Content="出力" Style="{StaticResource commandButtonStyle}" IsEnabled="{Binding CommandButtonEnabled}" Command="{Binding CommandButtonClickCommand}" CommandParameter="{Binding ElementName=myWindow}" Margin="0,0,4,4" HorizontalAlignment="Right" VerticalAlignment="Bottom" /> </Grid>
発見した解決方法
- キャストさせないように、object型で受け取るようにした
なんだかよくわからないけどキャストに失敗してるらしいから、
キャストさせないように、XAML側のCommandParameterのobject型に合わせ、
パラメータ受け取り側のパラメータの型をobject型にしてみたらうまくいった
DelegateCommand<MyWindow> → DelegateCommand<object>
- XAML側のバインド指定方法で、RelativeSourceを使った
RelativeSourceで同じオブジェクト内の別プロパティ(ここではWindow)を見つけさせたらよくわからないけどうまくいった
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
キャストに失敗 という判定が下る理由がまだよくわかってない。
その他試したけど無駄だったこと
- CanExecuteを用意していなかったため用意して、問答無用でtrueを返すようにした
https://msdn.microsoft.com/en-us/library/gg431410%28v=pandp.50%29.aspx
The constructor deliberately prevents the use of value types. Because ICommand takes an object, having a value type for T would cause unexpected behavior when CanExecute(null) is called during XAML initialization for command bindings.
Examples
public MyClass()
{
this.submitCommand = new DelegateCommand(this.Submit, this.CanSubmit);
}private bool CanSubmit(int? customerId)
{
return (customerId.HasValue && customers.Contains(customerId.Value));
}
英語力ないので誤訳してそうでアレですが、
値型は使うな。null許容型をparameterで渡すとよからぬことが起こりかねないから。
もし使いたいなら、CanExecute的なとこでチェックしろ と言っているのだと理解した。(すごく自信がない)
そして、例を見た感じ、CanSubmit(CanExecuteにあたるもの)でnullじゃなかったらtrueみたいに渡してるので
true判定を出せばとりあえずインスタンス化はできるのではないかと考えて実施
思うところ
- Object型がキャストできない型ってあるの?
- XAML側で型を指定することってできるの?
(調べた感じマークアップを使うのかな?でもDelegateCommandにはマークアップ拡張が用意されてなさそうな感じする)
- RelativeSource指定でうまくいくのは、AncestorType={x:Type Window}と指定していて、CommandParameterに入れた時点でWindow型になっている(???)からか
- 「InvalidCastException: 型 'Microsoft.VisualStudio.DesignTools.WpfDesigner.InstanceBuilders.WindowInstance' のオブジェクトを型 'testNamespace.MyWindow にキャストできません。」の型 'Microsoft.VisualStudio.DesignTools.WpfDesigner.InstanceBuilders.WindowInstance'ってデザイナー用の型っぽい。デザイナーはなんか違うオブジェクトを渡そうとしてる???
- 自分はやはりサーチ力英語力が低い。なかなかこれだという情報にたどり着けない。
ローカルリソースを参照できない
定義したローカルリソースを参照できない場面があったのでメモ
家にソース内からうろおぼえでかく
EasingColorKeyFrame.Value
<!-- ローカルリソースの定義(WindowResources内) --> <SolidBorderBrush x:Name="hogeColor" Value="#FF000000" /> <SolidBorderBrush x:Name="hogeColor2" Value="#AA000000" /> <!-- ローカルリソースを使おうとした場所(Style内) --> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x: Type Button}"> <Border x Name="buttonborder" Background="Transparent" BorderBrush="Transparent" BorderThickness="1" SnapsToDevicePixels="True"> <Border.Effect> <DropShadowEffect Opacity="0" /> </Border.Effect> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x Name="Normal"> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="buttonborder"> <EasingDoubleKeyFrame KeyTime="0" Value="0.6" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="MouseOver"> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="buttonborder"> <EasingDoubleKeyFrame KeyTime="0" Value="1" /> </DoubleAnimationUsingKeyFrames> <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="buttonborder"> <EasingColorKeyFrame KeyTime="0" Value="{StaticResource hogeColor}" /> </ColorAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x Name="Pressed"> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="buttonborder"> <EasingDoubleKeyFrame KeyTime="0" Value="1" /> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Effect).(DropShadowEffect.ShadowDepth)" Storyboard.TargetName="buttonborder"> <EasingDoubleKeyFrame KeyTime="0" Value="0" /> </DoubleAnimationUsingKeyFrames> <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Effect).(DropShadowEffect.Color)" Storyboard.TargetName="buttonborder"> <EasingColorKeyFrame KeyTime="0" Value="White" /> </ColorAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Effect).(DropShadowEffect.Opacity)" Storyboard.TargetName="buttonborder"> <EasingDoubleKeyFrame KeyTime="0" Value="0.6" /> </DoubleAnimationUsingKeyFrames> <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="buttonborder"> <EasingColorKeyFrame KeyTime="0" Value="{StaticResource hogeColor2}" /> </ColorAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x Name="Disabled" /> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <ContentPresenter x Name="buttoncontentPresenter" Focusable="False" Margin="{TemplateBinding Padding}" HorizontalAlignment="{ TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{ TemplateBinding VerticalContentAlignment}" /> </Border> </ControlTemplate> </Setter.Value> </Setter>
の
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="buttonborder"> <EasingColorKeyFrame KeyTime="0" Value="{StaticResource hogeColor}" /> </ColorAnimationUsingKeyFrames>
と
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="buttonborder"> <EasingColorKeyFrame KeyTime="0" Value="{StaticResource hogeColor2}" /> </ColorAnimationUsingKeyFrames>
あとこれもだめだったきがする。
DropShadowEffect.Color
<DropShadowEffect Color="{StaticResource hogeColor}" BlurRadius="{Binding ElementName=slider1, Path=Value}" ShadowDepth="{Binding ElementName=slider2, Path=Value}" Direction="{Binding ElementName=slider3, Path=Value}" />
この辺で躓いてるの、なんかXAMLの根幹をいろいろとわかってないのじゃないだろうか。。。
ResourceDictionary内でイベント的な処理を行う方法
- ResourceDictionaryのコードビハインドを用意する
- ResourceDictionaryのViewModelを用意し、Commandを実装する
Application.MainWindow プロパティの自動設定
同じチームの方に教えてもらった仕様
Application.MainWindow プロパティ (System.Windows)
MainWindow には、AppDomain でインスタンス化される最初の Window オブジェクトへの参照が自動的に設定されます。
え、マジかってなった。(常識かもしれないけど。。。)
MainWindowに設定しているWindowが、立ち上がるに、いろいろ起動前のチェック処理とかしてるんだけど、そのときに当然エラーや不正を確認した場合はメッセージ出して、アプリケーションを落とす等の処理を入れてるんですが。
そのエラーメッセージってのは、PrismのInteractionRequestでWindow継承したクラスで自作したエラーダイアログ表示してるんだけど、こいつがMainWindowに設定されてしまってこいつが表示されたらすぐに落ちてしまうってことが起こってたらしい。
しかも、同僚の方が実装してた処理はその処理によって、MainWindowの内容が変化するような処理だったから、チェック処理やその実装する処理の前にMainWindowをインスタンス化しても、処理の内容が反映されないからどうしようか。ってなっていたみたい。
以前僕が自分でOnStartupイベントハンドラ内をいじってたときに実装してた起動前のチェック処理は、ダイアログが表示された(エラーと判定された)ら、アプリケーションの起動はせずに落とす処理だったから、気づかなかった。
いや確かに、InteractionRequestでエラーダイアログを連続で2回表示する瞬間があったんだけど、なぜか2回目が表示されなくて、えーーなんでだなんでだってずっと悩んでたんだけど、この仕様を知って合点がいった。。。
じゃあ、どうすんのってことになって、結局、チェック処理の前に、一度MainWindowをインスタンス化してMainWindowプロパティを設定して、チェック処理が終わったら
新しくインスタンス化してそいつをMainWindowプロパティに設定し、一度目に設定したMainWindowをクローズさせるというなんともすっきりしない方法でとりあえず。
なかなか気持ち悪くて、なにかアプリケーション立ち上がる前に、エラーメッセージとか出す時の正しいやり方を知りたくて家帰ってからも少し調べたけど、結局わからなかったナー
常日頃思うけど、サーチ力とデバッグ力と、英語力が足らない。
今回は機械翻訳が割とまともだったから良いけど、日本語になってないの結構あるし英語読めるようにならなきゃなぁ~、とは思いつつもなかなか手がつかない。。。
今日はこんな感じ。
完璧主義者
自分の性格をコントロールできるようになりたい。
開発する速度 本当に遅いなぁ。って開発をしててふと感じました。
仕事が丁寧で遅い人に共通する、たった1つの問題点とその対策。 - プロジェクトマネジメントの話とか
このエントリによって、完璧主義者な自分が根強くいることを気づかされました。
心配性、不安症なのでしょうか。
開発でいえば、はじめは形なんて凝らなくていいから、なんでもいいから動くものをさっさとつくれ!!っていう罵声を浴びそうなくらい、導入部分から凝ろうとしてしまう。
たとえばだけど、
・MVVMを準拠しよ~
・クラスをしっかりわけよ~
・メンバのコードビハインドべたうちはやめよう~
・Commandを使おう~
・WPFで開発しよう~
とかとか。挙句の果てに、セオリー知らないことで悩むとセオリー調べ始める。
もうなんでもいいからとりあえず動くもの作るようにしようほんと。
開発だけじゃなくなんでもそうだけど、ビビったり完璧が良かったりで慎重になりすぎで全然行動できない自分がいる。
更に仕事のメンバーにも、こうしたほうがよいのかなぁああしたほうがよいのかなぁなんて言ってしまっていることもある。
これからは、できるかぎり割り切るようにしていこうと思う。