2017-10-04 18 views
1

WPFアプリケーションでは、マウスをクリックして操作し、ホットキーで操作する必要があります。ユーザーの操作は、アプリケーションコントロールのデータと外観の両方に影響します。WPF&MVVM:RelayCommand()からのコントロールへのアクセス

たとえば、の場合、次のアプリはティーマシンにデータを送信します。ティーブランド、タイプ(ホットまたはコールド)、オプション成分(ミルク、レモン、シロップ)を選択できます。

  • は、ドロップダウンメニューや入力Ctrl+Bをクリックした場合は、選択オプションのリストが表示されます:UI設計の観点から良いが、一例に過ぎない

    enter image description here

  • Ctrl+Tの「ホット」ボタンをクリックすると、ボタンが青色になり、テキストが「寒い」になります。 Ctrl+Tをクリックまたは入力すると、ボタンがオレンジになり、テキストが再び「ホット」になります。
  • オプションの成分ボタンをクリックするか、それぞれのショートカットを入力すると、ボタンの背景とテキストが灰色になります(「選択されていません」を意味します)。同じアクションは、それぞれのボタンをアクティブ状態に戻します。

enter image description here

MVVMを使用していないとのショートカットを定義しない場合、ロジックは比較的簡単になります。

Tea tea = new Tea(); // Assume that default settings avalible 

private void ToggleTeaType(object sender, EventArgs e){ 

    // Change Data 
    if(tea.getType().Equals("Hot")){ 
     tea.setType("Cold"); 
    } 
    else{ 
     tea.setType("Hot"); 
    } 

    // Change Button Appearence 
    ChangeTeaTypeButtonAppearence(sender, e); 
} 


private void ChangeTeaTypeButtonAppearence(object sender, EventArgs e){ 

    Button clickedButton = sender as Button; 
    Style hotTeaButtonStyle = this.FindResource("TeaTypeButtonHot") as Style; 
    Style coldTeaButtonStyle = this.FindResource("TeaTypeButtonCold") as Style; 

    if (clickedButton.Tag.Equals("Hot")) { 
     clickedButton.Style = coldTeaButtonStyle; // includes Tag declaration 
     clickedButton.Content = "Cold"; 
    } 
    else (clickedButton.Tag.Equals("Cold")) { 
     clickedButton.Style = hotTeaButtonStyle; // includes Tag declaration 
     clickedButton.Content = "Hot"; 
    } 
} 

// similarly for ingredients toggles 

はXAML:

<Button Content="Hot" 
      Tag="Hot" 
      Click="ToggleTeaType" 
      Style="{StaticResource TeaTypeButtonHot}"/> 

<Button Content="Milk" 
     Tag="True" 
     Click="ToggleMilk" 
     Style="{StaticResource IngredientButtonTrue}"/> 

<Button Content="Lemon" 
     Tag="True" 
     Click="ToggleLemon" 
     Style="{StaticResource IngredientButtonTrue}"/> 

<Button Content="Syrup" 
     Tag="True" 
     Click="ToggleSyrup" 
     Style="{StaticResource IngredientButtonTrue}"/> 

私は自分を変え同じようなWPFプロジェクトへのMVVMのおかげで簡単にショートカットを割り当てる:

<Window.InputBindings> 
    <KeyBinding Gesture="Ctrl+T" Command="{Binding ToggleTeaType}" /> 
</Window.InputBindings> 

ただし、コントロールの外観を設定する方法が問題になりました。次のコードは、無効です:

private RelayCommand toggleTeaType; 
public RelayCommand ToggleTeaType { 
    // change data by MVVM methods... 
    // change appearence: 
    ChangeTeaTypeButtonAppearence(object sender, EventArgs e); 
} 

私はRelayCommandからコントロールを表示するためにアクセスすることができますどのように私は、ボタンやショートカットの両方にバインドすることができますので、リレーのコマンドが必要ですが、?

+0

代わりにToggleButtonを使用してみませんか? IsToggledプロパティをデータにバインドし、必要に応じて再生できます。 – CKII

+3

'Background'などのボタンのプロパティを' Tea'クラスのプロパティにバインドすることができます。次に、コンバーターを使用して、プロパティの値に応じて背景色を設定します。 –

+0

通常、あなたのシナリオでは 'ToggleButton'を使うことをお勧めします。次に、 'IsChecked'プロパティを利用してMVVMを使用することができます。 – grek40

答えて

2

viewmodelはビュー固有の動作をきれいに保つ必要があります。 viewmodelだけで、関連するすべての設定のためのインタフェースを提供する必要があり、それは(BaseViewModelINotifyPropertyChangedなどを実装するために、いくつかのヘルパーメソッドが含まれます)、次のようになります:あなたが見たよう

public class TeaConfigurationViewModel : BaseViewModel 
{ 
    public TeaConfigurationViewModel() 
    { 
     _TeaNames = new string[] 
     { 
      "Lipton", 
      "Generic", 
      "Misc", 
     }; 
    } 
    private IEnumerable<string> _TeaNames; 
    public IEnumerable<string> TeaNames 
    { 
     get { return _TeaNames; } 
    } 


    private string _SelectedTea; 
    public string SelectedTea 
    { 
     get { return _SelectedTea; } 
     set { SetProperty(ref _SelectedTea, value); } 
    } 


    private bool _IsHotTea; 
    public bool IsHotTea 
    { 
     get { return _IsHotTea; } 
     set { SetProperty(ref _IsHotTea, value); } 
    } 


    private bool _WithMilk; 
    public bool WithMilk 
    { 
     get { return _WithMilk; } 
     set { SetProperty(ref _WithMilk, value); } 
    } 


    private bool _WithLemon; 
    public bool WithLemon 
    { 
     get { return _WithLemon; } 
     set { SetProperty(ref _WithLemon, value); } 
    } 


    private bool _WithSyrup; 
    public bool WithSyrup 
    { 
     get { return _WithSyrup; } 
     set { SetProperty(ref _WithSyrup, value); } 
    } 
} 

、それぞれのプロパティがありますビューモデルは気にしませんどのようにプロパティが割り当てられています。

これでUIを構築できます。次の例では、一般的に、xmlns:localがプロジェクトの名前空間を指しているとします。

私はあなたの目的のためにカスタマイズされたToggleButtonを利用勧め:

public class MyToggleButton : ToggleButton 
{ 
    static MyToggleButton() 
    { 
     MyToggleButton.DefaultStyleKeyProperty.OverrideMetadata(typeof(MyToggleButton), new FrameworkPropertyMetadata(typeof(MyToggleButton))); 
    } 


    public Brush ToggledBackground 
    { 
     get { return (Brush)GetValue(ToggledBackgroundProperty); } 
     set { SetValue(ToggledBackgroundProperty, value); } 
    } 

    // Using a DependencyProperty as the backing store for ToggledBackground. This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty ToggledBackgroundProperty = 
     DependencyProperty.Register("ToggledBackground", typeof(Brush), typeof(MyToggleButton), new FrameworkPropertyMetadata()); 
} 

そしてThemes/Generic.xaml中を:今

<Style TargetType="{x:Type local:MyToggleButton}" BasedOn="{StaticResource {x:Type ToggleButton}}"> 
    <Setter Property="Template"> 
     <Setter.Value> 
      <ControlTemplate TargetType="{x:Type local:MyToggleButton}"> 
       <Border x:Name="border1" BorderBrush="Gray" BorderThickness="1" Background="{TemplateBinding Background}" Padding="5"> 
        <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> 
       </Border> 
       <ControlTemplate.Triggers> 
        <Trigger Property="IsChecked" Value="True"> 
         <Setter TargetName="border1" Property="Background" Value="{Binding ToggledBackground,RelativeSource={RelativeSource TemplatedParent}}"/> 
        </Trigger> 
       </ControlTemplate.Triggers> 
      </ControlTemplate> 
     </Setter.Value> 
    </Setter> 
</Style> 

、このトグルボタンを使用して実際のウィンドウのコンテンツを構築します。これは、ラベルや説明もなしのみの機能コントロールを含む、ご希望のUIだけのラフスケッチです:HotCold間のボタン内容を変更する

<Grid x:Name="grid1"> 
    <StackPanel> 
     <StackPanel Orientation="Horizontal"> 
      <ComboBox 
       x:Name="cb1" 
       VerticalAlignment="Center" 
       IsEditable="True" 
       Margin="20" 
       MinWidth="200" 
       ItemsSource="{Binding TeaNames}" 
       SelectedItem="{Binding SelectedTea}"> 
      </ComboBox> 
      <local:MyToggleButton 
       x:Name="hotToggle" 
       IsChecked="{Binding IsHotTea}" 
       VerticalAlignment="Center" 
       Margin="20" MinWidth="60" 
       Background="AliceBlue" ToggledBackground="Orange"> 
       <local:MyToggleButton.Style> 
        <Style TargetType="{x:Type local:MyToggleButton}"> 
         <Setter Property="Content" Value="Cold"/> 
         <Style.Triggers> 
          <Trigger Property="IsChecked" Value="True"> 
           <Setter Property="Content" Value="Hot"/> 
          </Trigger> 
         </Style.Triggers> 
        </Style> 
       </local:MyToggleButton.Style> 
      </local:MyToggleButton> 
     </StackPanel> 
     <StackPanel Orientation="Horizontal"> 
      <local:MyToggleButton 
       x:Name="milkToggle" 
       Content="Milk" 
       IsChecked="{Binding WithMilk}" 
       VerticalAlignment="Center" 
       Margin="20" MinWidth="60" 
       Background="WhiteSmoke" ToggledBackground="LightGreen"/> 
      <local:MyToggleButton 
       x:Name="lemonToggle" 
       Content="Lemon" 
       IsChecked="{Binding WithLemon}" 
       VerticalAlignment="Center" 
       Margin="20" MinWidth="60" 
       Background="WhiteSmoke" ToggledBackground="LightGreen"/> 
      <local:MyToggleButton 
       x:Name="syrupToggle" 
       Content="Syrup" 
       IsChecked="{Binding WithSyrup}" 
       VerticalAlignment="Center" 
       Margin="20" MinWidth="60" 
       Background="WhiteSmoke" ToggledBackground="LightGreen"/> 
     </StackPanel> 
    </StackPanel> 
</Grid> 

お知らせスタイルのトリガー。

は、(例えば、ウィンドウコンストラクタで)どこかのDataContextを初期化し

public MainWindow() 
{ 
    InitializeComponent(); 

    grid1.DataContext = new TeaConfigurationViewModel(); 
} 

この時点で、あなたは完全に機能するUIを持って、それがデフォルトのマウスとキーボードの入力方法で動作しますが、それは」勝ちましたまだあなたのショートカットキーをサポートしていません。

したがって、既に動作しているUIを破壊することなくキーボードショートカットを追加できます。

<Window.CommandBindings> 
    <CommandBinding Command="local:AutomationCommands.OpenList" Executed="OpenList_Executed"/> 
    <CommandBinding Command="local:AutomationCommands.ToggleHot" Executed="ToggleHot_Executed"/> 
    <CommandBinding Command="local:AutomationCommands.ToggleMilk" Executed="ToggleMilk_Executed"/> 
    <CommandBinding Command="local:AutomationCommands.ToggleLemon" Executed="ToggleLemon_Executed"/> 
    <CommandBinding Command="local:AutomationCommands.ToggleSyrup" Executed="ToggleSyrup_Executed"/> 
</Window.CommandBindings> 

をし、各ショートカットでの適切なハンドラメソッドを実装します。

public static class AutomationCommands 
{ 
    public static RoutedCommand OpenList = new RoutedCommand("OpenList", typeof(AutomationCommands), new InputGestureCollection() 
    { 
     new KeyGesture(Key.B, ModifierKeys.Control) 
    }); 

    public static RoutedCommand ToggleHot = new RoutedCommand("ToggleHot", typeof(AutomationCommands), new InputGestureCollection() 
    { 
     new KeyGesture(Key.T, ModifierKeys.Control) 
    }); 

    public static RoutedCommand ToggleMilk = new RoutedCommand("ToggleMilk", typeof(AutomationCommands), new InputGestureCollection() 
    { 
     new KeyGesture(Key.M, ModifierKeys.Control) 
    }); 

    public static RoutedCommand ToggleLemon = new RoutedCommand("ToggleLemon", typeof(AutomationCommands), new InputGestureCollection() 
    { 
     new KeyGesture(Key.L, ModifierKeys.Control) 
    }); 

    public static RoutedCommand ToggleSyrup = new RoutedCommand("ToggleSyrup", typeof(AutomationCommands), new InputGestureCollection() 
    { 
     new KeyGesture(Key.S, ModifierKeys.Control) 
    }); 
} 

あなたは、あなたのメインウィンドウで、適切なアクションにそれらのコマンドをバインドすることができます。一つのアプローチは、いくつかのカスタムコマンドを作成して使用することであり、後ろの窓コード:再び

private void OpenList_Executed(object sender, ExecutedRoutedEventArgs e) 
{ 
    FocusManager.SetFocusedElement(cb1, cb1); 
    cb1.IsDropDownOpen = true; 
} 

private void ToggleHot_Executed(object sender, ExecutedRoutedEventArgs e) 
{ 
    hotToggle.IsChecked = !hotToggle.IsChecked; 
} 

private void ToggleMilk_Executed(object sender, ExecutedRoutedEventArgs e) 
{ 
    milkToggle.IsChecked = !milkToggle.IsChecked; 
} 

private void ToggleLemon_Executed(object sender, ExecutedRoutedEventArgs e) 
{ 
    lemonToggle.IsChecked = !lemonToggle.IsChecked; 
} 

private void ToggleSyrup_Executed(object sender, ExecutedRoutedEventArgs e) 
{ 
    syrupToggle.IsChecked = !syrupToggle.IsChecked; 
} 

は、それが表示さpropertを変更するだけで別の方法で、純粋にUI関連している事を結合この全体の入力を覚えていますユーザーがマウスでボタンをクリックした場合と同じバインドで変更がビューモデルに転送されます。そのようなものをビューモデルに持ち込む理由はありません。

+1

お返事ありがとうございました。私は私のプロジェクトをリファクタリングしようとします。私がこの問題に対処すれば、ここで(コメントで)もう一度答えてください。 –

+0

申し訳ありませんが、 'xxx_Executed'メソッドはどこに宣言する必要がありますか? 'AutomationCommands'クラスでは? –

+0

@GurebuBofofu私はWindowクラスで宣言しました。 'CommandBindings'が宣言されている場所によって異なります。ショートカットをウィンドウ全体で機能させたいので、ウィンドウ上でコマンドを定義するのが最も理にかなっています。私の場合は – grek40

1

どのようにRelayCommandからViewコントロールにアクセスできますか?

すべきではありません。 MVVMの全ポイント(おそらく)は、懸念を分けることです。 ViewModelに含まれる '状態'は、ビュー(コントロール)によってレンダリングされます。 ViewModel /ロジックは、ビューを直接決して調整してはいけません。これにより、懸念の分離が破られ、ロジックがレンダリングに密接に結合されるためです。

ビューには、ビューモデルでの状態の表示方法をレンダリングする必要があります。

通常、これはバインディングによって行われます。例:ViewModelがテキストボックス参照を取得して文字列:myTextBox.SetText("some value")を設定するのではなく、ビューモデル内のプロパティMyTextにビューをバインドします。

画面上に物を表示する方法を決定するのはビューの責任です。

それはすべてうまくいいですが、どうですか?

<Button Content="Hot" 
     Tag="Hot" 
     Click="ToggleTeaType" 
     Style="{Binding TeaType, Converter={StaticResource TeaTypeButtonStyleConverter}}"/> 

:私はあなたが記述のようなスタイルを使用して、この変更を行いたい場合は、私はViewModelにに結合状態を(たとえば、列挙型プロパティHotまたはCold)を使用して変換するコンバータを使用して試してみた、示唆しますWPFのバインディングを使用しています。私たちがモデルを見ている唯一の参考資料は、プロパティーTeaTypeです。

あなたの静的リソースに定義され

、我々はコンバータを内蔵しています:

<ResourceDictionary> 
     <Style x:Key="HotTeaStyle"/> 
     <Style x:Key="ColdTeaStyle"/> 

     <local:TeaTypeButtonStyleConverter 
      x:Key="TeaTypeButtonStyleConverter" 
      HotStateStyle="{StaticResource HotTeaStyle}" 
      ColdStateStyle="{StaticResource ColdTeaStyle}"/> 
    </ResourceDictionary> 

そして、これにスタイルにTeaType列挙型から変換するためのロジックを持っている:

public enum TeaType 
{ 
    Hot, Cold 
} 

class TeaTypeButtonStyleConverter : IValueConverter 
{ 
    public Style HotStateStyle { get; set; } 
    public Style ColdStateStyle { get; set; } 

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     TeaType teaType = (TeaType)value; 

     if (teaType == TeaType.Hot) 
     { 
      return HotStateStyle; 
     } 
     else if (teaType == TeaType.Cold) 
     { 
      return ColdStateStyle; 
     } 
     return null; 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     throw new NotSupportedException(); 
    } 
} 

それはより多くを作ることができますジェネリックで再利用可能。

トグルボタンも見てください。内部でこのようなことを処理します。

+0

ありがとうございます。私はあなたの答えを実践することで最善を尽くします。 –

+0

私はもう一つの答えがトグルボタンのためのより良い解決策だと思います。しかし、コンバーターが存在することを覚えておいてください。MVVMで本当に役に立ちます。 – Joe

+0

OKです。アドバイスをいただきありがとうございます。 –

関連する問題