ViewModelから処理中ダイアログを表示する
Microsoft.TeamFoundation.MVVMのWindowViewModelを使うと、WindowDisplayServiceでできる。
以前は、ViewModelにViewのDispatcher.Invokeしたり、Viewとの共通クラスをViewModelから使ったりしてたが、それはMVVMと言えない。
「ViewModelからViewを操作する」とか「ViewModelでMessageBoxを出す」とかでググり続けて・・・
MVVMインフラとかBlendのSDKとかいまいちムズいがこれなら簡単っぽい。
最近作ってる「イベントログからPCの稼働時間を取得するアプリ」に組み込んでみた。
スタイルはMDSNのProgressBar のスタイルとテンプレートから。
カッコいいな。( ´∀`)bグッ!
手順は、
プロジェクトの参照設定で、[アセンブリ]-[拡張]の中から”Microsoft.TeamFoundation.Controls”を追加し、
呼び出しソースは、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<Window : xmlns:mvvm="clr-namespace:Microsoft.TeamFoundation.MVVM;assembly=Microsoft.TeamFoundation.Controls" mvvm:MVVMSupport.ViewModel="{Binding}" : xmlns:local ="clr-namespace:PCTime.View" : > <Window.DataContext> <vm:EventLogViewModel /> </Window.DataContext> <Window.Resources> : <mvvm:RegisterWindow x:Key="ProgressWindowKey" Type="local:ProgressWindow" /> : </Window.Resources> |
1 2 3 4 5 6 7 8 9 10 |
using Microsoft.TeamFoundation.MVVM; : namespace PCTime.ViewModel { class EventLogViewModel : WindowViewModel { : // 処理中ダイアログ表示 WindowDisplayService.ShowDialog("ProgressWindowKey", vm); |
これだけ。
XAMLにRegisterWindowを書いて、Microsoft.TeamFoundation.MVVMのWindowViewModelを継承したViewModelからWindowDisplayService.ShowDialogするだけ。
処理中ダイアログは、Actionを受け取るようにして、コンストラクタでTaskでActionを実行。終了orキャンセルで画面を閉じる。
画面を閉じるのは、WindowViewModelのRequestClose()を呼べばよい。
WindowViewModelにはRelayCommandも実装されてるので、キャンセルボタンのハンドラもViewModelに書ける。
コードビハインドには何も書かなくてよい。枠なしWindowの移動処理をコードビハインドに書いてるだけ。かなりMVVMっぽくなった。
以下、ソース。
処理中ウィンドウ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<Window x:Class="PCTime.View.ProgressWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mvvm="clr-namespace:Microsoft.TeamFoundation.MVVM;assembly=Microsoft.TeamFoundation.Controls" mvvm:MVVMSupport.ViewModel="{Binding}" xmlns:local="clr-namespace:PCTime.View" Title="ProgressWindow" Height="190" Width="448" Style="{StaticResource windowStyleProgress}" MouseLeftButtonDown="Window_MouseLeftButtonDown" WindowStartupLocation="CenterOwner" > <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="12*"/> <ColumnDefinition Width="415*"/> <ColumnDefinition Width="13*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="11*"/> <RowDefinition Height="44*"/> <RowDefinition Height="10*"/> <RowDefinition Height="33*"/> <RowDefinition Height="61*"/> </Grid.RowDefinitions> <ProgressBar Grid.Row="3" Grid.Column="1" Maximum="{Binding Max}" Value="{Binding Value}" IsIndeterminate="True"/> <Button Content="キャンセル" Grid.Column="1" HorizontalAlignment="Center" Height="38" Grid.Row="4" VerticalAlignment="Center" Style="{StaticResource buttonStyleBlue}" Width="114" Command="{Binding CancelCommand}"/> <TextBlock Grid.Column="1" TextWrapping="Wrap" Grid.Row="1" FontSize="20" Text="{Binding Message}"/> </Grid> </Window> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/// <summary> /// ProgressWindow.xaml の相互作用ロジック /// </summary> public partial class ProgressWindow : Window { public ProgressWindow() { InitializeComponent(); } private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { //マウスボタン押下状態でなければ何もしない if (e.ButtonState != MouseButtonState.Pressed) return; this.DragMove(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
class ProgressWindowViewModel : WindowViewModel { public ProgressWindowViewModel() { // キャンセルボタン押下時のコマンド登録 CancelCommand = new RelayCommand(() => { Result = MessageBoxResult.Cancel; this.RequestClose(); }); } /// <summary> /// コンストラクタ /// </summary> /// <param name="action"></param> /// <param name="message"></param> public ProgressWindowViewModel(Action action, string message) :this() { this.Message = message; bool isCompleted = false; // 重い処理 Task.Factory.StartNew(() => { action(); isCompleted = true; }); var cancelTokenSource = new CancellationTokenSource(); Task task = Task.Factory.StartNew(() => { int interval = 100; while (true) { // 重い処理完了待ち if (isCompleted) { break; } Thread.Sleep(interval); if (cancelTokenSource.IsCancellationRequested) { Debug.WriteLine("キャンセルされました。"); return; } } // 処理完了時ダイアログを閉じる(WindowViewModelのResultに値を設定する) this.Dispatcher.Invoke(new Action(() => { this.Result = MessageBoxResult.OK; this.RequestClose(); })); }); if (this.Result.Equals(MessageBoxResult.Cancel)) { cancelTokenSource.Cancel(); } } /// <summary> /// CancelCommand /// </summary> public ICommand CancelCommand { get; private set; } private string _Message; /// <summary> /// Message /// </summary> public string Message { get { return _Message; } set { _Message = value; RaisePropertyChanged("Message"); } } /// <summary> /// Result /// </summary> public MessageBoxResult Result { get; set; } } |
呼び出し元
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
: GetData(StartDate, EndDate); : /// <summary> /// イベントログのデータを取得する /// </summary> public void GetData(DateTime startDate, DateTime endDate) { string results = string.Empty; List<EventLogDataModel.EventDateTimeData> list = null; bool bRes = ProgressBar( () => { results = EventLogDataModel.GetEventLog(startDate, endDate); } , "イベントログ取得処理中"); // 処理完了時 if (bRes) { // 結果処理 bRes = ProgressBar( () => { list = EventLogDataModel.MakeData(results); } , "結果整形処理中"); } if (bRes) { this.DateTimeDatas = new ObservableCollection<EventLogDataModel.EventDateTimeData>(list); } } /// <summary> /// 処理中ダイアログ表示 /// </summary> /// <param name="action"></param> /// <param name="message"></param> private bool ProgressBar(Action action, string message) { var vm = new ProgressWindowViewModel(action, message); // 処理中ダイアログ表示 WindowDisplayService.ShowDialog("ProgressWindowKey", vm); return vm.Result == MessageBoxResult.OK ? true : false; } |
スタイル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="StyleColors.xaml"/> </ResourceDictionary.MergedDictionaries> <!-- ProgressBar のスタイルとテンプレート https://msdn.microsoft.com/ja-jp/library/ms750638%28v=vs.110%29.aspx --> <Style x:Key="{x:Type ProgressBar}" TargetType="{x:Type ProgressBar}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ProgressBar}"> <Grid MinHeight="14" MinWidth="200" Background="{TemplateBinding Background}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Determinate" /> <VisualState x:Name="Indeterminate"> <Storyboard> <ObjectAnimationUsingKeyFrames Duration="00:00:00" Storyboard.TargetName="PART_Indicator" Storyboard.TargetProperty="Background"> <DiscreteObjectKeyFrame KeyTime="00:00:00"> <DiscreteObjectKeyFrame.Value> <SolidColorBrush>Transparent</SolidColorBrush> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Border x:Name="PART_Track" CornerRadius="2" BorderThickness="1"> <Border.BorderBrush> <SolidColorBrush Color="{DynamicResource BorderMediumColor}" /> </Border.BorderBrush> </Border> <Border x:Name="PART_Indicator" CornerRadius="2" BorderThickness="1" HorizontalAlignment="Left" Background="{TemplateBinding Foreground}" Margin="0,-1,0,1"> <Border.BorderBrush> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientBrush.GradientStops> <GradientStopCollection> <GradientStop Color="{DynamicResource BorderLightColor}" Offset="0.0" /> <GradientStop Color="{DynamicResource BorderMediumColor}" Offset="1.0" /> </GradientStopCollection> </GradientBrush.GradientStops> </LinearGradientBrush> </Border.BorderBrush> <Grid ClipToBounds="True" x:Name="Animation"> <Rectangle x:Name="PART_GlowRect" Width="100" HorizontalAlignment="Left" Fill="{StaticResource ProgressBarIndicatorAnimatedFill}" Margin="-100,0,0,0" /> </Grid> </Border> </Grid> </ControlTemplate> </Setter.Value> </Setter> <Setter Property="Background"> <Setter.Value> <LinearGradientBrush EndPoint="0,1" StartPoint="0,0"> <GradientStop Color="{DynamicResource ControlLightColor}" Offset="0" /> <GradientStop Color="{DynamicResource ControlMediumColor}" Offset="1" /> </LinearGradientBrush> </Setter.Value> </Setter> <Setter Property="Foreground"> <Setter.Value> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="{DynamicResource ControlMediumColor}" Offset="0" /> <GradientStop Color="{DynamicResource ControlDarkColor}" Offset="1" /> </LinearGradientBrush> </Setter.Value> </Setter> </Style> </ResourceDictionary> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <!--Control colors.--> <Color x:Key="WindowColor">#FFE8EDF9</Color> <Color x:Key="ContentAreaColorLight">#FFC5CBF9</Color> <Color x:Key="ContentAreaColorDark">#FF7381F9</Color> <Color x:Key="DisabledControlLightColor">#FFE8EDF9</Color> <Color x:Key="DisabledControlDarkColor">#FFC5CBF9</Color> <Color x:Key="DisabledForegroundColor">#FF888888</Color> <Color x:Key="SelectedBackgroundColor">#FFC5CBF9</Color> <Color x:Key="SelectedUnfocusedColor">#FFDDDDDD</Color> <Color x:Key="ControlLightColor">White</Color> <Color x:Key="ControlMediumColor">#FF7381F9</Color> <Color x:Key="ControlDarkColor">#FF211AA9</Color> <Color x:Key="ControlMouseOverColor">#FF3843C4</Color> <Color x:Key="ControlPressedColor">#FF211AA9</Color> <Color x:Key="GlyphColor">#FF444444</Color> <Color x:Key="GlyphMouseOver">sc#1, 0.004391443, 0.002428215, 0.242281124</Color> <!--Border colors--> <Color x:Key="BorderLightColor">#FFCCCCCC</Color> <Color x:Key="BorderMediumColor">#FF888888</Color> <Color x:Key="BorderDarkColor">#FF444444</Color> <Color x:Key="PressedBorderLightColor">#FF888888</Color> <Color x:Key="PressedBorderDarkColor">#FF444444</Color> <Color x:Key="DisabledBorderLightColor">#FFAAAAAA</Color> <Color x:Key="DisabledBorderDarkColor">#FF888888</Color> <Color x:Key="DefaultBorderBrushDarkColor">Black</Color> <!--Control-specific resources.--> <Color x:Key="HeaderTopColor">#FFC5CBF9</Color> <Color x:Key="DatagridCurrentCellBorderColor">Black</Color> <Color x:Key="SliderTrackDarkColor">#FFC5CBF9</Color> <Color x:Key="NavButtonFrameColor">#FF3843C4</Color> <LinearGradientBrush x:Key="MenuPopupBrush" EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="{DynamicResource ControlLightColor}" Offset="0" /> <GradientStop Color="{DynamicResource ControlMediumColor}" Offset="0.5" /> <GradientStop Color="{DynamicResource ControlLightColor}" Offset="1" /> </LinearGradientBrush> <LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill" StartPoint="0,0" EndPoint="1,0"> <LinearGradientBrush.GradientStops> <GradientStopCollection> <GradientStop Color="#000000FF" Offset="0" /> <GradientStop Color="#600000FF" Offset="0.4" /> <GradientStop Color="#600000FF" Offset="0.6" /> <GradientStop Color="#000000FF" Offset="1" /> </GradientStopCollection> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </ResourceDictionary> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <!-- プログレスウィンドウのスタイル --> <Style x:Key="windowStyleProgress" TargetType="{x:Type Window}" BasedOn="{StaticResource {x:Type Window}}" > <Setter Property="Background" Value="Transparent"/> <Setter Property="AllowsTransparency" Value="True"/> <Setter Property="Foreground" Value="white"/> <Setter Property="WindowStyle" Value="None"/> <Setter Property="ResizeMode" Value="NoResize"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Window}"> <Border BorderBrush="Black" BorderThickness="1" CornerRadius="10"> <Border.Background> <LinearGradientBrush EndPoint="0.5,1" MappingMode="RelativeToBoundingBox" StartPoint="0.5,0"> <GradientStop Color="{DynamicResource {x:Static SystemColors.GradientInactiveCaptionColorKey}}" Offset="1"/> <GradientStop Color="{DynamicResource {x:Static SystemColors.HotTrackColorKey}}"/> </LinearGradientBrush> </Border.Background> <ContentPresenter /> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <!-- ボタン青 --> <Style x:Key="buttonStyleBlue" TargetType="{x:Type Button}" > <Setter Property="FontSize" Value="16" /> <Setter Property="Background" Value="#00000000"/> <Setter Property="Foreground" Value="white"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Border Name="border" BorderThickness="0" BorderBrush="Transparent"> <Border.Background> <ImageBrush ImageSource="/PCTime;component/View/Images/btn053_06.png" /> </Border.Background> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="border" Property="Background" > <Setter.Value> <ImageBrush ImageSource="/PCTime;component/View/Images/btn053_05.png" /> </Setter.Value> </Setter> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter TargetName="border" Property="Background" > <Setter.Value> <ImageBrush ImageSource="/PCTime;component/View/Images/btn053_06_push.png" /> </Setter.Value> </Setter> </Trigger> <Trigger Property="IsEnabled" Value="false"> <Setter TargetName="border" Property="Background" > <Setter.Value> <ImageBrush ImageSource="/PCTime;component/View/Images/btn053_09.png" /> </Setter.Value> </Setter> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!-- ボタン赤 --> <Style x:Key="buttonStyleRed" TargetType="{x:Type Button}" > <Setter Property="FontSize" Value="16" /> <Setter Property="Background" Value="#00000000"/> <Setter Property="Foreground" Value="white"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Border Name="border" BorderThickness="0" BorderBrush="Transparent"> <Border.Background> <ImageBrush ImageSource="/PCTime;component/View/Images/btn053_01.png" /> </Border.Background> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="border" Property="Background" > <Setter.Value> <ImageBrush ImageSource="/PCTime;component/View/Images/btn053_02.png" /> </Setter.Value> </Setter> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter TargetName="border" Property="Background" > <Setter.Value> <ImageBrush ImageSource="/PCTime;component/View/Images/btn053_01_push.png" /> </Setter.Value> </Setter> </Trigger> <Trigger Property="IsEnabled" Value="false"> <Setter TargetName="border" Property="Background" > <Setter.Value> <ImageBrush ImageSource="/PCTime;component/View/Images/btn053_09.png" /> </Setter.Value> </Setter> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary> |
App.xaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<Application x:Class="PCTime.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="/View/EventLogViewWindow.xaml"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="View/Style/StyleButton.xaml" /> <ResourceDictionary Source="View/Style/StyleWindow.xaml" /> <ResourceDictionary Source="View/Style/StyleProgressBar.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application> |