【问题标题】:Applying MVVM pattern in a WPF application在 WPF 应用程序中应用 MVVM 模式
【发布时间】:2023-04-06 13:06:01
【问题描述】:

我正在制作一个简单的 WPF 应用程序,其中包含在画布上绘制的形状。该视图由一个地图组成,该地图在地图上的不同静态位置上具有若干正方形的复杂序列。

MapView 是一个包含视图框和画布的用户控件。正方形由带有简单画布和形状(代码中的椭圆)的 UserControl 表示:

<Canvas>
    <Canvas.Resources>
        <BooleanToVisibilityConverter x:Key="boolToVisibility" />
    </Canvas.Resources>

    <Ellipse Stroke="Black" Fill="{Binding Color}" Width="{Binding Dimension}" Height="{Binding Dimension}" />
    <Ellipse Stroke="Black" Fill="Black" Canvas.Top="15" Canvas.Left="15" Width="20" Height="20" Visibility="{Binding IsOccupied, Converter={StaticResource boolToVisibility}}" />

</Canvas>

这些视图显然都有一个 ViewModel(通过视图的 DataContext 属性绑定),由模型支持。

我的问题:

  • 我地图上的 SquareViews 都有一个 mousedown 事件,每个视图都代表一个模型,我对如何在我的应用程序中以优雅的方式实现这一点感到困惑(关于 MVVM 模式)。我应该在 XAML 中预定义 SquareViews,然后生成模型,还是预先生成模型,然后根据运行时对我的模型所做的更改动态创建视图。

  • 如何区分 SquareViews?基于(视图)模型参考?位置坐标?我想避免给每个单独的方格一个单独的名称...

  • 将视图的 DataContext 设置为其对应的视图模型(无需使用框架)的其他方式,而不是将其添加到视图的代码隐藏中。

  • 有没有更好的方法在我的地图上定位方块? (我知道画布在缩放、不同分辨率、dpi 等方面不是很灵活,但据说视图框应该可以改善这一点,尽管我还没有完全测试过)

PS 如果我的描述/问题含糊/抽象,请告诉我..

【问题讨论】:

    标签: c# wpf xaml mvvm


    【解决方案1】:

    如果我理解你的问题....

    我认为您可以采用的方法是使用 DataTemplates、ItemsControl 和 ContentPresentor。

    实际上,您想要做的是告诉 WPF “显示”您的视图模型。因为您的视图模型只是普通的类,所以 WPF 不知道如何“渲染”它们。这就是 DataTemplates 的用武之地。这种方法的好处是 DataTemplate 内容的 DataContext 将自动设置为视图模型。 DataTemplates 在您的 Window 或 UserControl 资源中定义:

    <Window.Resources>
        <DataTemplate DataType="{x:Type ViewModels:SquareViewModel}">
            <Views:SquareView />
        </DataTemplate>
    </Window.Resources>
    

    当 WPF 遇到 SquareViewModel 时,上面的代码将呈现一个 SquareView 用户控件(并将 SquareView 上的 DataContext 设置为 SquareViewModel)。

    要将视图模型放置在视图中,您可以使用 ContentPresenter(用于单个 ViewModel):

    <ContentPresenter Content="{Binding SingleSquare}" />
    

    在您的情况下,您将希望显示 SquareViewModel 项目的集合,因此您将希望使用 ItemsControl:

    <ItemsControl ItemsSource="{Binding Squares}" />
    

    但是,这不会给您想要的结果,因为默认情况下它会像 ListBox 一样。您需要将模板应用到 ItemsControl 以使用基础 Canvas。请参阅this blog by Pete Brown 了解可能的实现。

    祝你好运!

    编辑:附加代码示例


    查看模型:

    public class MainViewModel
    {
        public IEnumerable<SquareViewModel> Squares { get; set; }
    
        public MainViewModel()
        {
            var squares = new List<SquareViewModel>();
            squares.Add(new SquareViewModel(15, 15,100,100, Brushes.CadetBlue, "Square One"));
            squares.Add(new SquareViewModel(75,125, 80, 80, Brushes.Indigo, "Square Two"));
            Squares = squares;
        }
    }
    
    public class SquareViewModel
    {
        public int X { get; set; }
        public int Y { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public Brush Color { get; set; }
        public string Name { get; set; }
    
        public SquareViewModel(int x, int y, int width, int height, Brush color, string name)
        {
            X = x;
            Y = y;
            Width = width;
            Height = height;
            Color = color;
            Name = name;
        }
    }
    

    观看次数

    <UserControl x:Class="MapView.Views.SquareView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        >
        <Grid Background="{Binding Color, FallbackValue=Azure}">
            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding Name, FallbackValue=None}" />     
        </Grid>
    </UserControl>
    
    <Window x:Class="MapView.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:ViewModels="clr-namespace:MapView.ViewModels" 
        xmlns:Views="clr-namespace:MapView.Views" Title="Window1" Height="300" Width="300">
        <Window.Resources>
            <DataTemplate DataType="{x:Type ViewModels:SquareViewModel}">
                <Views:SquareView />
            </DataTemplate>
        </Window.Resources>
        <ItemsControl ItemsSource="{Binding Squares}" >
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Beige" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemContainerStyle>
                <Style TargetType="ContentPresenter">
                    <Setter Property="Canvas.Left"
                        Value="{Binding X}" />
                    <Setter Property="Canvas.Top"
                        Value="{Binding Y}" />
                    <Setter Property="Width"
                        Value="{Binding Width}" />
                    <Setter Property="Height"
                        Value="{Binding Height}" />
                </Style>
            </ItemsControl.ItemContainerStyle>
        </ItemsControl>
    </Window>
    

    在 Window1 构造函数中:

    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            DataContext = new MainViewModel();
        }
    }
    

    【讨论】:

    • 如何在您的解决方案中生成(视图)模型?我不确定如何实现这一点,你能用额外的代码澄清一下吗?
    • 您通常需要将 DataContext 显式设置为视图模型至少一次。在我的示例中,我将创建一个 MainViewModel 并设置 MainWindow.DataContext = new MainViewModel()。 MainViewModel 有一个属性集合 IEnumerable Squares { get;放;} 。因为我已将我的 ItemsSource 绑定到 Squares WPF 将要渲染这些正方形。由于它们只是类,它将使用 DataTemplate 来确定它应该实际呈现 SquareView(自动将每个 SquareView 的 datacontext 设置为关联的 SquareViewModel)
    • 在 Answer 中添加了额外的代码示例。请注意,我使用的是 Window,但您可以使用 MapView 用户控件。如果您有一个用户控件,那么在“托管”该用户控件的视图上,您​​可以使用 ContentPresenter 绑定到 MapViewModel 并拥有一个将 MapViewModel 与 MapView 匹配的 DT(然后不需要显式设置 DataContext那里)。
    • 非常感谢您帮助我!用模型实现这一点也很容易吗?现在我的正方形模型通过链表连接(实际上有几种类型的正方形,它们是通过继承定义的)。一个正方形也可以有几个状态,这些状态也应该反映在视图中。地图模型还有几个附加属性,以及几个方形模型引用(在我的视图中充当入口点),我仍然不太确定如何将其与视图模型结合。跨度>
    【解决方案2】:

    看看 Ryan Cromwell 的 blog。基本上,您想在画布上显示正方形的“列表”。他解释了如何做我认为你想要的。

    【讨论】:

      【解决方案3】:

      你必须想出某种网格结构(WPF Datagrid 会为你做)。网格的优点是它可以像行可以被视为 x 坐标和列一样被用作 y 坐标。在开始实施之前,想象一下您想要在 UI 上显示什么。 WPF 只是一种将您的想象变为现实的技术。如果您的应用程序是 UI 驱动的,那么首先从 UI 收集所有可能的操作,在视图模型中为这些操作创建命令。

      【讨论】:

        猜你喜欢
        • 2018-04-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-22
        • 2011-09-13
        • 1970-01-01
        • 2013-09-29
        相关资源
        最近更新 更多