【问题标题】:How to Implement OnPreviewMouseDown without Code-Behind?如何在没有代码隐藏的情况下实现 OnPreviewMouseDown?
【发布时间】:2019-11-29 01:50:23
【问题描述】:

我需要将我的代码设为 MVVM(这意味着没有植入代码隐藏),并且我想在任何点击更改我的 LabelBackground >(即使我点击按钮)。

这意味着我需要从我的代码隐藏中删除我的 MainWindowView_OnPreviewMouseDownMainWindowView_OnPreviewMouseUp

这是我的工作项目:

代码隐藏

    public partial class MainWindowView : Window
    {

        private readonly MainWindowViewModel _viewModel;
        public MainWindowView()
        {
            InitializeComponent();
            _viewModel = new MainWindowViewModel();
            // The DataContext serves as the starting point of Binding Paths
            DataContext = _viewModel;
        }

        private void MainWindowView_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            _viewModel.LabelBackground = Brushes.Black;
        }

        private void MainWindowView_OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            _viewModel.LabelBackground = Brushes.Blue;
        }
    }
}

XAML

<Window x:Class="WpfExample.MainWindowView" PreviewMouseDown="MainWindowView_OnPreviewMouseDown" PreviewMouseUp="MainWindowView_OnPreviewMouseUp"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        xmlns:local="clr-namespace:WpfExample">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Grid.Row="0" Command="{Binding HiButtonCommand}" CommandParameter="Hai" Content="click to hi"
                Height="100" Margin="208,30,203,30"  />
        <Label Content="Disco Background" Background="{Binding LabelBackground}" Margin="208,66,203,69" Grid.Row="1"/>
    </Grid>
</Window>

视图模型

class MainWindowViewModel : INotifyPropertyChanged
{
    private ICommand hiButtonCommand;
    public ICommand HiButtonCommand
    {
        get
        { return hiButtonCommand; }
        set
        { hiButtonCommand = value; }
    }

    private SolidColorBrush labelBackground;
    public SolidColorBrush LabelBackground
    {
        get { return labelBackground; }
        set
        {
            if (labelBackground != value)
            {
                labelBackground = value;
                OnPropertyChanged("LabelBackground");
            }
        }
    }

    public MainWindowViewModel()
    {
        HiButtonCommand = new RelayCommand(ShowMessage);
    }

    public void ShowMessage(object obj)
    {
        MessageBox.Show(obj.ToString());
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

最好的方法是什么?

编辑:

我尝试使用EventTrigger 方法,但对我来说效果不佳,因为 它不会改变标签背景,它会改变背景 取而代之的是窗口。有什么方法可以定位标签而不是 窗户?

XAML:

<Window x:Class="WpfExample.MainWindowView" PreviewMouseDown="MainWindowView_OnPreviewMouseDown" PreviewMouseUp="MainWindowView_OnPreviewMouseUp"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        xmlns:local="clr-namespace:WpfExample">

    <Window.Triggers>
        <EventTrigger RoutedEvent="Mouse.PreviewMouseDown">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation To="Black" 
                                    Storyboard.TargetProperty="(Label.Background).(SolidColorBrush.Color)" 
                                    Duration="0"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>

        <EventTrigger RoutedEvent="Mouse.PreviewMouseUp">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation To="Blue" 
                                    Storyboard.TargetProperty="(Label.Background).(SolidColorBrush.Color)" 
                                    Duration="0"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Window.Triggers>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Grid.Row="0" Command="{Binding HiButtonCommand}" CommandParameter="Hai" Content="click to hi" Height="100" Margin="208,30,203,30"/>
        <Label Name="Label" Content="Disco Background" Background="White" Margin="208,66,203,59" Grid.Row="1"/>
    </Grid>
</Window>

【问题讨论】:

  • MVVM 是关于将视图与模型分离。这与从视图或代码隐藏中消除 C# 无关。代码隐藏很好,但在有 XAML 解决方案时通常可以而且应该避免,因为 XAML 简单且易于阅读(因为标记反映了可视树结构)。 XAML 解析器为您做了很多工作,因此无需使用 C# 手动执行操作。有些事情必须在代码隐藏中完成。例如,大多数与控件的视觉效果没有直接关系的逻辑。避免代码隐藏是件好事,但并非总是可行。
  • 由于不清楚您的处理程序在做什么,我无法解决您的具体情况。但我通常可以解决您的代码示例:使用 XAML 而不是 C# 创建视图模型实例,将视图模型分配给 XAML 中的 DataContext 而不是 C#。考虑使用EventTriggers 处理路由事件,例如PreviewMouseDown
  • 我尝试使用EventTrigger 方法,但它对我来说效果不佳,因为它不会更改标签背景,而是更改窗口的背景。有没有办法定位标签而不是窗口? (见编辑问题)@BionicCode
  • 只需将以下附加属性添加到每个ColorAnimationStoryboard.TargetName="Label"

标签: c# wpf mvvm data-binding code-behind


【解决方案1】:

这是一项简单的任务,没有最好的方法。

  1. 使用 fody https://github.com/Fody/Home/ 更改属性 https://github.com/Fody/PropertyChanged
  2. 创建实现通知属性更改的基类视图
  3. 使用支持 MV* 模式的框架,例如 Caliburn.Micro https://caliburnmicro.com/documentation/cheat-sheet

并且代码应该是:

XAML

<Button 
    cal:Message.Attach="[Event MouseDown] = [Action Action1]" 
    cal:Message.Attach="[Event MouseUp] = [Action Action2]" 
    Content="{Binding ButtonContent}" />
<Label 
    Content="Disco Background" 
    Background="{Binding Importance, Converter={StaticResource ImportanceToBgConverter}}"/>

视图模型

    public String ButtonContent { get; set; } = "click to hi";
    public ImportanceType Importance { get; set; }
    public void Action1()
    {
        Importance = Importance.NotImportant;
    }

    public void Action2()
    {
        Importance = Importance.Important;
    }

【讨论】:

  • 我认为这不是好的 MVVM。颜色与视图相关(在大多数情况下),应该定义为资源,可能在专用程序集中。在您的示例中,视图模型处理 UI 逻辑。这违反了干净的分隔您的示例可以使用 XAML 中的EventTrigger 轻松实现。此时不需要任何框架。
  • PreviewMouseDown 是一个路由事件是有充分理由的。它完全与视图相关,应该在 XAML 中处理(路由事件的事件处理的首选选择是 EventTrigger 处理程序)。
  • @BionicCode 这很好,您可以想象它不是从硬编码值中读取值,而是从数据库或资源字典(仅在代码中)读取值。是的,使用数据触发器或事件触发器,您也可以创建仅 XAML 的解决方案,在这种情况下,您通常会将其保留在资源文件中。通常你会想动态改变主题,一个很好的例子:github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit
  • 我不认为这完全没问题。视图模型不应该知道 UI 元素有哪些颜色。 UI 的颜色应该取决于资源键。然后在初始化时,您会将数据库中的颜色添加到ResourceDictionary。因此,每种颜色都必须映射到一个资源键。让视图模型负责为 UI 着色是完全错误的。这引入了太紧的耦合。鼠标事件与 UI 相关,仅此而已。鼠标、键盘或触摸是旨在与 UI 交互而不是与模型或视图模型交互的用户输入设备。
  • @BionicCode 我的观点是,通过这个简单的示例,我演示了如何跳过文件背后的代码,并简单地将事件绑定到他可以传递参数的视图模型方法。
【解决方案2】:

我的新 XAML 解决了这个问题 不再需要代码隐藏:

新 XAML:

<Window x:Class="WpfExample.MainWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        xmlns:local="clr-namespace:WpfExample">

    <Window.Triggers>
        <EventTrigger RoutedEvent="Mouse.PreviewMouseDown">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation To="Black" 
                                    Storyboard.TargetProperty="(Label.Background).(SolidColorBrush.Color)" 
                                    Storyboard.TargetName="Label"
                                    Duration="0"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>

        <EventTrigger RoutedEvent="Mouse.PreviewMouseUp">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation To="Blue" 
                                    Storyboard.TargetProperty="(Label.Background).(SolidColorBrush.Color)"
                                    Storyboard.TargetName="Label"
                                    Duration="0"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Window.Triggers>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Grid.Row="0" Command="{Binding HiButtonCommand}" CommandParameter="Hai" Content="click to hi" Height="100" Margin="208,30,203,30"/>
        <Label Name="Label" Content="Disco Background" Background="White" Margin="208,66,203,59" Grid.Row="1"/>
    </Grid>
</Window>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-07
    • 2022-11-22
    • 1970-01-01
    • 2013-01-20
    相关资源
    最近更新 更多