webentwicklung-frage-antwort-db.com.de

WPF/MVVM - Wie gehe ich mit Doppelklick auf TreeViewItems im ViewModel um?

(Hinweis - dies ist eine erneute Veröffentlichung, da meine erste Frage unter einer falschen Überschrift veröffentlicht wurde: hier Entschuldigung!)

Ich besitze eine Standard-WPF-Strukturansicht und habe Elemente zum Anzeigen von Modellklassen gebunden.

Ich möchte jetzt das Verhalten beim Doppelklicken von Elementen behandeln (Öffnen von Dokumenten im Visual Studio-Stil).

Ich kann die Ereignisbehandlungsroutine veranlassen, im Steuerelement mit der Strukturansicht (xaml) zu feuern, aber wie binde ich an ein bestimmtes Verhalten in den Ansichtsmodellklassen - z. ProjectViewModel?

Am liebsten an ICommand-Implementer gebunden, da dies an anderer Stelle verwendet wird ...

<TreeView ItemsSource="{Binding Projects}" MouseDoubleClick="TreeView_MouseDoubleClick">
    <TreeView.ItemContainerStyle>
        <!-- 
This Style binds a TreeViewItem to a TreeViewItemViewModel. 
-->
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
            <Setter Property="FontWeight" Value="Normal" />
            <Style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="FontWeight" Value="Bold" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </TreeView.ItemContainerStyle>

    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type Implementations:ProjectViewModel}" ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal">
                <Image Width="16" Height="16" Margin="3,0" Source="Images\Region.png" />
                <TextBlock Text="{Binding DisplayName}" />
            </StackPanel>
        </HierarchicalDataTemplate>

        <HierarchicalDataTemplate DataType="{x:Type Implementations:PumpViewModel}" ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal">
                <Image Width="16" Height="16" Margin="3,0" Source="Images\State.png" />
                <TextBlock Text="{Binding Name}" />
            </StackPanel>
        </HierarchicalDataTemplate>

        <DataTemplate DataType="{x:Type Implementations:PumpDesignViewModel}">
            <StackPanel Orientation="Horizontal">
                <Image Width="16" Height="16" Margin="3,0" Source="Images\City.png" />
                <TextBlock Text="{Binding Name}" />
            </StackPanel>
        </DataTemplate>
    </TreeView.Resources>
</TreeView>
27
Anders Juul

Meine Antwort ein wenig aktualisieren.

Ich habe viele verschiedene Ansätze dafür ausprobiert und fühle mich immer noch wie Attached Behaviors ist die beste Lösung. Obwohl es am Anfang wie eine Menge Overhead aussieht, ist es wirklich nicht der Fall. Ich behalte alle meine Verhaltensweisen für ICommands an derselben Stelle. Wann immer ich Unterstützung für ein anderes Ereignis benötige, ist es nur eine Frage von Kopieren/Einfügen und Ändern des Ereignisses in der PropertyChangedCallback

Ich habe auch die optionale Unterstützung für CommandParameter hinzugefügt.  

Beim Designer geht es nur darum, das gewünschte Event auszuwählen

enter image description here

Sie können dies entweder auf TreeView, TreeViewItem oder an einem anderen Ort einstellen, den Sie mögen. 

Beispiel. Setze es auf die TreeView

<TreeView commandBehaviors:MouseDoubleClick.Command="{Binding YourCommand}"
          commandBehaviors:MouseDoubleClick.CommandParameter="{Binding}"
          .../>

Beispiel. Setze es auf TreeViewItem

<TreeView ItemsSource="{Binding Projects}">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="commandBehaviors:MouseDoubleClick.Command"
                    Value="{Binding YourCommand}"/>
            <Setter Property="commandBehaviors:MouseDoubleClick.CommandParameter"
                    Value="{Binding}"/>
        </Style>
    </TreeView.ItemContainerStyle>
</TreeView>

Und hier ist das Attached Behavior MouseDoubleClick

public class MouseDoubleClick
{
    public static DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached("Command",
        typeof(ICommand),
        typeof(MouseDoubleClick),
        new UIPropertyMetadata(CommandChanged));

    public static DependencyProperty CommandParameterProperty =
        DependencyProperty.RegisterAttached("CommandParameter",
                                            typeof(object),
                                            typeof(MouseDoubleClick),
                                            new UIPropertyMetadata(null));

    public static void SetCommand(DependencyObject target, ICommand value)
    {
        target.SetValue(CommandProperty, value);
    }

    public static void SetCommandParameter(DependencyObject target, object value)
    {
        target.SetValue(CommandParameterProperty, value);
    }
    public static object GetCommandParameter(DependencyObject target)
    {
        return target.GetValue(CommandParameterProperty);
    }

    private static void CommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        Control control = target as Control;
        if (control != null)
        {
            if ((e.NewValue != null) && (e.OldValue == null))
            {
                control.MouseDoubleClick += OnMouseDoubleClick;
            }
            else if ((e.NewValue == null) && (e.OldValue != null))
            {
                control.MouseDoubleClick -= OnMouseDoubleClick;
            }
        }
    }

    private static void OnMouseDoubleClick(object sender, RoutedEventArgs e)
    {
        Control control = sender as Control;
        ICommand command = (ICommand)control.GetValue(CommandProperty);
        object commandParameter = control.GetValue(CommandParameterProperty);
        command.Execute(commandParameter);
    }
}
51
Fredrik Hedblad

Ich bin spät dran, aber ich habe nur eine andere Lösung verwendet. Wieder einmal ist es vielleicht nicht das Beste, aber so habe ich das gemacht.

Zunächst ist die vorige Antwort von Meleak cool, aber ich finde es sehr schwer, AttachedBehaviors nur für etwas so grundlegendes wie einen MouseDoubleClick hinzufügen zu müssen. Dies würde mich zwingen, ein neues Muster in meiner App zu verwenden, und würde alles noch komplizierter machen.

Mein Ziel ist es, so einfach wie möglich zu bleiben. Deshalb habe ich etwas sehr grundlegendes getan (mein Beispiel ist für ein DataGrid, aber Sie können das für viele verschiedene Steuerelemente verwenden):

<DataGrid MouseDoubleClick="DataGrid_MouseDoubleClick">
   <!-- ... -->
</DataGrid>

Im Code-Behind:

private void DataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    //Execute the command related to the doubleclick, in my case Edit
    (this.DataContext as VmHome).EditAppCommand.Execute(null);
}

Warum habe ich das Gefühl, dass es das MVVM-Muster nicht bricht? Da meiner Meinung nach die einzigen Dinge, die Sie in den Code-Behind einfügen sollten, Brücken zu Ihrem viewModel sind, Dinge, die sehr spezifisch für Ihre Benutzeroberfläche sind. In diesem Fall heißt es lediglich, dass der zugehörige Befehl ausgelöst wird, wenn Sie doppelt klicken. Es ist fast dasselbe wie ein Befehl = "{Binding EditAppCommand}", ich habe dieses Verhalten simuliert.

Fühlen Sie sich frei, mir Ihre Meinung dazu zu sagen. Ich würde mich freuen, einige Kritiker dieser Denkweise zu hören, aber im Moment glaube ich, dass dies der einfachste Weg ist, sie umzusetzen, ohne die MVVM zu beschädigen.

9
Damascus

Sowohl die Empfehlungen von Meleak als auch von ígor sind großartig, aber wenn der DoppelklickEreignishandler an TreeViewItem gebunden ist, dann wird dieser - Ereignishandler für alle übergeordneten Elemente des Elements aufgerufen (nicht nur für das angeklickte Element). Wenn es nicht erwünscht ist, ist hier noch ein weiterer Zusatz:

private static void OnMouseDoubleClick(object sender, RoutedEventArgs e)
{
    Control control = sender as Control;
    ICommand command = (ICommand)control.GetValue(CommandProperty);
    object commandParameter = control.GetValue(CommandParameterProperty);

    if (sender is TreeViewItem)
    {
        if (!((TreeViewItem)sender).IsSelected)
            return;
    }

    if (command.CanExecute(commandParameter))
    {
        command.Execute(commandParameter);
    }
}
5
hightower70

Es ist wirklich einfach und so habe ich mit Doppelklick auf die Baumansicht umgegangen:

<Window x:Class="TreeViewWpfApplication.MainWindow"
    ...
    xmlns:i="clr-namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity"
    xmlns:ei="http://schemas.Microsoft.com/expression/2010/interactions"
    ...>

      <TreeView ItemsSource="{Binding Departments}" >
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="MouseDoubleClick">
                <ei:CallMethodAction MethodName="SomeMethod" TargetObject="{Binding}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
      </TreeView>
</Window>

System.Windows.Interactivity.dll stammt aus C:\Programme (x86)\Microsoft SDKs\Expression\Blend.NETFramework\v4.0\Libraries\System.Windows.Interactivity .dll oder von NuGet

Mein Ansichtsmodell:

public class TreeViewModel : INotifyPropertyChanged
{   
    private List<Department> departments;
    public TreeViewModel()
    {
        Departments = new List<Department>()
        {
            new Department("Department1"),
            new Department("Department2"),
            new Department("Department3")
        };
    }

    public List<Department> Departments
    {
        get
        {
            return departments;
        }
        set
        {
            departments = value;
            OnPropertyChanged("Departments");
        }
    }

    public void SomeMethod()
    {
        MessageBox.Show("*****");
    }
}   
3
StepUp

Meleak Lösung ist großartig!, Aber ich habe Check hinzugefügt 

    private static void OnMouseDoubleClick(object sender, RoutedEventArgs e)
    {
        Control control = sender as Control;
        ICommand command = (ICommand)control.GetValue(CommandProperty);
        object commandParameter = control.GetValue(CommandParameterProperty);
       //Check command can execute!!  
      if(command.CanExecute(commandParameter ))
         command.Execute(commandParameter);
    }
2
ígor

Der beste Ansatz, den ich erreicht habe, ist das Binden der IsSelected -Eigenschaft von TreeViewItem an das ViewModel in einem bidirektionalen Modus und Implementieren der Logik im Eigenschaftssetter. Sie können dann definieren, was zu tun ist, wenn der Wert true oder false ist, da sich diese Eigenschaft ändert, wenn der Benutzer auf ein Element klickt.

class MyVM
{
  private bool _isSelected;
  public bool IsSelected
  {
    get { return _isSelected; }
    set
    {
      if (_isSelected == null)
       return;

      _isSelected = vale;

      if (_isSelected)
      {
        // Your logic goes here.
      }
      else
      {
        // Your other logic goes here.
      }
   }
}

Dadurch wird viel Code vermieden.

Mit dieser Technik können Sie das "Onclick" -Verhalten nur in den ViewModels implementieren, die es wirklich benötigen.

0
JoanComasFdz

Mausbindung auf dem TextBlock

In den TreeView.Resources der Ansicht:

   <HierarchicalDataTemplate 
      DataType="{x:Type treeview:DiscoveryUrlViewModel}" 
      ItemsSource="{Binding Children}">

      <StackPanel Orientation="Horizontal">
           <Image Width="16" Height="16" Margin="3,0" Source="../Images/ic_search.png" />

           <TextBlock Text="{Binding DisplayText}" >
               <TextBlock.InputBindings>
                     <MouseBinding Gesture="LeftDoubleClick"
                                   Command="{Binding DoubleClickCopyCommand}"
                                   CommandParameter="{Binding }" />
               </TextBlock.InputBindings>
            </TextBlock>
       </StackPanel>
 </HierarchicalDataTemplate>

Im ViewModel dieser Ansicht (DiscoveryUrlViewModel.cs):

private RelayCommand _doubleClickCommand;   
public ICommand DoubleClickCopyCommand
        {
            get
            {
                if (_doubleClickCommand == null)
                    _doubleClickCommand = new RelayCommand(OnDoubleClick);
                return _doubleClickCommand;
            }
        }

        private void OnDoubleClick(object obj)
        {
            var clickedViewModel = (DiscoveryUrlViewModel)obj;
        }
0
Pinfi

Nur aus Neugier: Was ist, wenn ich Frederiks teilnehme, es aber direkt als Verhalten umsetze?

public class MouseDoubleClickBehavior : Behavior<Control>
{
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register("Command", typeof (ICommand), typeof (MouseDoubleClickBehavior), new PropertyMetadata(default(ICommand)));

    public ICommand Command
    {
        get { return (ICommand) GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    public static readonly DependencyProperty CommandParameterProperty =
        DependencyProperty.Register("CommandParameter", typeof (object), typeof (MouseDoubleClickBehavior), new PropertyMetadata(default(object)));

    public object CommandParameter
    {
        get { return GetValue(CommandParameterProperty); }
        set { SetValue(CommandParameterProperty, value); }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.MouseDoubleClick += OnMouseDoubleClick;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.MouseDoubleClick -= OnMouseDoubleClick;
        base.OnDetaching();
    }

    void OnMouseDoubleClick(object sender, RoutedEventArgs e)
    {
        if (Command == null) return;
        Command.Execute(/*commandParameter*/null);
    }
}
0
Slesa