【问题标题】:WPF AvalonDock LayoutChanged and LayoutChanging MVVMWPF AvalonDock LayoutChanged 和 LayoutChanging MVVM
【发布时间】:2019-09-18 07:29:17
【问题描述】:

我在我的 MVVM WPF 应用程序中使用 AvalonDock 组件。在我的 XAML 中,我有这样的东西:

        <xcad:DockingManager Name="_dockingManager" Margin="5" Grid.Row="2"
                         DataContext="{Binding DockingManagerViewModel}"
                         DocumentsSource="{Binding Documents}"
                         ActiveContent="{Binding Path=ActiveContent, Mode=TwoWay}"
                         AnchorablesSource="{Binding Anchorables}">

现在我想对布局更改做出反应。如上面的 XAML sn-p 所示,我已将 DockingManager 绑定到“Doc​​kingManagerViewModel”。所以我假设也在我的视图模型中处理布局更改。主要问题是停靠管理器提供了一个 LayoutChanging 和 LayoutChanged 事件,我不知道如何在我的视图模型中处理它。我想我无法将这些事件绑定到我的视图模型中的相应命令?知道处理这个问题的最佳方法是什么吗?

为了更好地理解,我想要实现如下:用户显示一个“属性”窗口,然后将窗口从右侧拖到左侧。之后,用户关闭“属性”窗口,并在用户决定再次显示属性窗口后不久。在这种情况下,我想带回左侧的窗口,因为这是最后一个位置。所以我的想法是在布局更改期间将最后一个位置存储在视图模型中,以便在再次显示视图时恢复该位置。

【问题讨论】:

  • 布局发生变化时你想做什么?它应该只影响视图还是底层数据(视图模型)?我想你可以在前者中有一些代码隐藏,或者在后者中绑定到你的视图模型中的命令。
  • @CorentinPane:用户可以在应用程序的生命周期内显示或隐藏视图。如果用户在之前隐藏后再次显示视图,则该视图应该出现在与之前完全相同的位置。因此,我认为我可以将这些信息存储在视图模型中以便稍后检索它
  • 我并不特别了解 xcad:DockingManager,但您为什么要听 LayoutChanged 事件来了解是否应该隐藏或显示您的视图?用户实际上将如何隐藏或显示视图?通过点击按钮?
  • 向视图添加事件处理程序并从那里执行视图模型命令?
  • @CorentinPane:我已经更新了上面的描述以澄清我的问题

标签: c# wpf mvvm avalondock xceed


【解决方案1】:

所以你想对 UI 中发生的一些事件做出反应。首先要做的事情是:如果您想仅更改视图/布局,则不需要ICommand 并且一个简单的事件处理程序就可以解决问题。如果您希望更改底层数据(您的视图模型)以响应该事件,您可以使用ICommand 或事件处理程序,如下所述。

我们先为MainWindow定义一个简单的视图模型:

public class MyViewModel {
    /// <summary>
    /// Command that performs stuff.
    /// </summary>
    public ICommand MyCommand { get; private set; }

    public MyViewModel() {
        //Create the ICommand
        MyCommand = new RelayCommand(() => PerformStuff());
    }

    public void PerformStuff() {
        //Do stuff that affects your view model and model.
        //Do not do anything here that needs a reference to a view object, as this breaks MVVM.
        Console.WriteLine("Stuff performed in ViewModel.");
    }
}

这假设您有一个 ICommand 接口的 RelayCommand 实现,可让您在 Execute 上调用 Action 委托:

public class RelayCommand : ICommand {
    //Saved Action to invoke on Execute
    private Action _action;

    /// <summary>
    /// ICommand that always runs the passed <paramref name="action"/> when executing.
    /// </summary>
    /// <param name="action"></param>
    public RelayCommand(Action action) {
        _action = action;
    }

    #region ICommand
    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter) => true;

    public void Execute(object parameter) => _action.Invoke();
    #endregion
}

在您的 MainWindow.xaml 中,我们定义了两个 Border 对象:第一个通过事件处理程序对视图模型进行操作,第二个通过 ICommand 模式:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
    <!--Performs action on click through event handler-->
    <Border Grid.Row="0" MouseUp="Border_MouseUp" Background="Red"></Border>
    <!--Performs action on click through ICommand-->
    <Border Grid.Row="1" Background="Blue">
        <Border.InputBindings>
            <MouseBinding MouseAction="LeftClick" Command="{Binding MyCommand}"></MouseBinding>
        </Border.InputBindings>
    </Border>
</Grid>

在您的 MainWindow.xaml.cs 中,分配一个视图模型对象并为鼠标向上事件定义事件处理程序:

public partial class MainWindow : Window {
    public MainWindow() {
        InitializeComponent();
        DataContext = new MyViewModel(); ;
    }

    //Handles mouse up event on the first Border
    private void Border_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) {
        //...
        //Do stuff that affects only your view here!
        //...
        //Now the stuff that affects the view model/model:
        ((MyViewModel)DataContext).PerformStuff();
    }
}

单击两个Border 对象中的任何一个都会在您的视图模型上执行某些操作。

如何将此应用于您的特定控件和事件?

  • 您始终可以使用自定义事件处理程序DockingManager_LayoutChanged,如上所示。

  • 如果你想使用ICommand并且你的事件不是鼠标或键盘事件,你可以通过this而不是MouseBinding来实现绑定。

【讨论】:

    【解决方案2】:

    对于这样的场景,我总是写附加属性。

    例如,对于窗口的 Loaded-Event,我使用以下附加属性:

    internal class WindowExtensions
    {
        public static readonly DependencyProperty WindowLoadedCommandProperty = DependencyProperty.RegisterAttached(
            "WindowLoadedCommand", typeof(ICommand), typeof(WindowExtensions), new PropertyMetadata(default(ICommand), OnWindowLoadedCommandChanged));
    
        private static void OnWindowLoadedCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Window window = d as Window;
            if (window == null)
                return;
    
            if (e.NewValue is bool newValue)
            {
                if (newValue)
                {
                    window.Loaded += WindowOnLoaded;
                }
            }
        }
    
        private static void WindowOnLoaded(object sender, RoutedEventArgs e)
        {
            Window window = sender as Window;
            if (window == null)
                return;
    
            ICommand command = GetWindowLoadedCommand(window);
            command.Execute(null);
        }
    
        public static void SetWindowLoadedCommand(DependencyObject element, ICommand value)
        {
            element.SetValue(WindowLoadedCommandProperty, value);
        }
    
        public static ICommand GetWindowLoadedCommand(DependencyObject element)
        {
            return (ICommand) element.GetValue(WindowLoadedCommandProperty);
        }
    }
    

    在视图模型中,您有一个标准命令,例如:

    private ICommand loadedCommand;
    public ICommand LoadedCommand
    {
        get { return loadedCommand ?? (loadedCommand = new RelayCommand(Loaded)); }
    }
    
    private void Loaded(object obj)
    {
        // Logic here
    }
    

    在您编写的 XAML 中的 Window-Element 上:

    local:WindowExtensions.WindowLoadedCommand="{Binding LoadedCommand}"
    

    local 是 WindowExtensions 所在的命名空间

    【讨论】:

      猜你喜欢
      • 2010-12-13
      • 1970-01-01
      • 1970-01-01
      • 2011-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-16
      相关资源
      最近更新 更多