繁体   English   中英

xaml 中的命令绑定与 MVVM

[英]Command Binding in xaml with MVVM

目前我正在尝试使用 C#、WPF、MVVM 创建我的第一个 Windows 应用程序,见下图。

主窗口

我的文件结构:

文件结构

该应用程序的目标是在左侧有一个导航菜单,可用于导航到必须在白色区域显示的不同内容(见上图)。

导航菜单中的“MenuItems”(左侧6个按钮)用于更改白色区域中显示的内容。 我使用以下代码创建这些按钮:

在我的 MainWindow.xaml 中,我使用“ItemsControl”将“ItemSource”绑定到 MenuItems 列表。

MainWindows.xaml:

<Window x:Class="TSD.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TSD"
    xmlns:ViewModels="clr-namespace:TSD.ViewModels"
    xmlns:views="clr-namespace:TSD.Views"
    mc:Ignorable="d"
    Title="TS Dashboard" Height="600" Width="1025" WindowStartupLocation="CenterScreen"
    WindowStyle="None"
    ResizeMode="CanResize"
    WindowState="Normal"
    >

<Window.Resources>
</Window.Resources>

<WindowChrome.WindowChrome>
    <WindowChrome 
    CaptionHeight="0"
    ResizeBorderThickness="5"/>
</WindowChrome.WindowChrome>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="25"/>
        <RowDefinition Height="auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Background="#2b6ea4" MouseDown="Window_MouseDown">
        <TextBlock x:Name="mainwindow_title" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="20,0,0,0" Text="{Binding MainWindowText}">
        </TextBlock>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Width="75">
            <Button Background="Transparent" BorderThickness="0" Width="25" Name="min_button" Click="MinimizeButton_Click">
                <Image Source="/assets/mini.png" Height="10" Width="10"/>
            </Button>
            <Button Background="Transparent" BorderThickness="0" Width="25" Name="max_button" Click="MaximizeButton_Click">
                <Image Source="/assets/max.png" Height="10" Width="10"/>
            </Button>
            <Button Background="Transparent" BorderThickness="0" Width="25" Name="close_button" Click="CloseButton_Click">
                <Image Source="/assets/close.png" Height="10" Width="10"/>
            </Button>
        </StackPanel>
    </Grid>

    <Grid Grid.Column="0" Margin="0,0,0,0" Grid.Row="2">
        
        <Grid.RowDefinitions>
            <RowDefinition Height="100"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        
        <Image Grid.Row="0" Source="{Binding ImagePath}"></Image>

        <ItemsControl Background="#2B6EA4" Grid.Row="1" x:Name="NavigationMenu" ItemsSource="{Binding MenuItems}" Width="auto" HorizontalContentAlignment="Stretch">
            
        </ItemsControl>
        
    </Grid>
    
    <Grid Grid.Column="1" Margin="0,0,0,0" Grid.Row="2">
        <ContentControl Content="{Binding CurrentViewModel}">
            <ContentControl.Resources>
                <DataTemplate DataType="{x:Type ViewModels:HcfContentViewModel}">
                    <views:UserControlHcfContent/>
                </DataTemplate>
                <DataTemplate DataType="{x:Type ViewModels:SLDContentViewModel}">
                    <views:UserControlSLDContent/>
                </DataTemplate>
            </ContentControl.Resources>
        </ContentControl>
    </Grid>
    
</Grid>

我使用后面的代码将 MainWindowViewModel 设置为 DataContext。

MainWindow.xaml.cs:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        NavigationStore navigationStore = new NavigationStore();
        MainWindowViewModel MWVM = new MainWindowViewModel(navigationStore);
        //MWVM.CurrentViewModel = new SLDContentViewModel();
        //MWVM.CurrentViewModel = new HcfContentViewModel();

        InitializeComponent();
        DataContext = MWVM;
    }


    //Window control buttons title bar
    private void CloseButton_Click(object sender, RoutedEventArgs e)
    //Close button function
    {
        Close();
    }

    private void MinimizeButton_Click(object sender, RoutedEventArgs e)
    //Minimize button
    {
        this.WindowState = WindowState.Minimized;
    }

    private void MaximizeButton_Click(object sender, RoutedEventArgs e)
    //Maximize button
    {
        if (this.WindowState == WindowState.Normal)
        {
            this.WindowState = WindowState.Maximized;
        }
        else if (this.WindowState == WindowState.Maximized)
        {
            this.WindowState = WindowState.Normal;
        }
    }

    private void Window_MouseDown(object sender, MouseButtonEventArgs e)
    //draggable window
    {
        if (e.ChangedButton == MouseButton.Left)
            this.DragMove();
    }


} //Class

在我的 MainWindowViewModel 中,我创建了一个 UserControls 列表,每个 UserControl 代表导航菜单中的一个按钮,这个列表称为 _menuItems 并绑定到前面显示的 ItemsControl。

MainWindowViewModel.cs:

class MainWindowViewModel : BaseViewModel, INotifyPropertyChanged
{

    public MainWindowViewModel(NavigationStore navigationStore)
    {
        //Set name for window.
        _MainWindowText = "My first MVVM application";
        _ImagePath = "/assets/placeholder_image_name.png";

        //Create MenuItems
        UserControlMenuItem Item1 = new UserControlMenuItem(dummyCommand, "ISSUE LIST", "/assets/issue_list_icon.png");
        UserControlMenuItem Item2 = new UserControlMenuItem(dummyCommand, "SYSTEM SHORTLOG DOWNLOAD","/assets/sld_icon.jpg");
        UserControlMenuItem Item3 = new UserControlMenuItem(navigateHcfCommand, "HOSE CONNECTION FINDER", "/assets/hose_icon.png");
        UserControlMenuItem Item4 = new UserControlMenuItem(dummyCommand, "GET CONFIG", "/assets/config_icon.png");
        UserControlMenuItem Item5 = new UserControlMenuItem(dummyCommand, "KNOWLEDGE TRANSFER", "/assets/knowledge_transfer_icon.png");
        UserControlMenuItem Item6 = new UserControlMenuItem(dummyCommand, "AMBITION LISTS", "/assets/ambition_list_icon.png");
        
        //Properties
        _MenuItems = new List<UserControlMenuItem> { Item1, Item2, Item3, Item4, Item5, Item6 };
        _navigationStore = navigationStore;

        //Commands used in this view
        dummyCommand = new DummyCommand();
        navigateHcfCommand = new ShowHcfCommand(_navigationStore);
    }

    // Class fields
    private string _MainWindowText;
    private List<UserControlMenuItem> _MenuItems;
    private string _ImagePath;
    private readonly NavigationStore _navigationStore;
    // Commands
    public ICommand navigateHcfCommand;
    public ICommand dummyCommand;


    //Class properties
    public string ImagePath
    {
        get => _ImagePath;
        set {
            _ImagePath = ImagePath;
            OnPropertyChanged("ImagePath");
        }
    }

    public string MainWindowText
    {
        get => _MainWindowText;
        set {
            _MainWindowText = value;
            OnPropertyChanged("MainWindowText");
        }
    }

    public List<UserControlMenuItem> MenuItems
    {
        get => _MenuItems;
        set {
            _MenuItems = value;
            OnPropertyChanged("MenuItems");
        }
    }

    public BaseViewModel CurrentViewModel
    {
        get => _navigationStore.CurrentViewModel;
        set { _navigationStore.CurrentViewModel = value; }
    }

    public ICommand UpdateCommand
    {
        get;
        private set;
    }

    //
    // Events
    //

    //Declare the event
    public event PropertyChangedEventHandler PropertyChanged;

    // Create the OnPropertyChanged method to raise the event
    // The calling member's name will be used as the parameter.
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}// Class

UserControlMenuItem(定义按钮的布局)这个 UserControl 被添加到 MainWindowViewModel 中的 _menuItems 列表中:

<UserControl x:Class="TSD.Views.UserControlMenuItem"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:TSD.Views"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">

<Border BorderBrush="White" BorderThickness="0,0,0,1">
    <Button Background="Transparent" BorderThickness="0" Command="{Binding Command}">

        <Grid>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <Grid Grid.Column="0"  Background="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="10,10,10,10">
                <Image Source="{Binding MenuItem.ItemImage}" HorizontalAlignment="Left" Width="30" Height="30"></Image>
            </Grid>

            <Grid Grid.Column="1">
                <Label Content="{Binding MenuItem.ItemName}" Width="200" Height="50" VerticalContentAlignment="Center" HorizontalContentAlignment="Left"
               Foreground="black"
               Margin="0,2,2,0">

                </Label>
            </Grid>

        </Grid>

    </Button>

</Border>

UserControlMenuItem 有一个 MenuItemViewModel.cs,其中包含以下代码:

class MenuItemViewModel:BaseViewModel
{
    public MenuItemViewModel(string ItemName, string ItemImage, ICommand Command)
    {
        _MenuItem = new MenuItemModel(ItemName, ItemImage);
        _command = Command;
    }

    private MenuItemModel _MenuItem;
    public ICommand _command;

    public MenuItemModel MenuItem
    {
        get { return _MenuItem; }
    }

    public ICommand Command
    {
        get { return _command; }
    }


} // Class

和 MenuItemModel.cs:

class MenuItemModel : INotifyPropertyChanged
{

    private string _ItemName;
    private string _ItemImage;

    public MenuItemModel(string itemname, string itemimage)
    {
        _ItemName = itemname;
        _ItemImage = itemimage;
    }

    public string ItemName
    {
        get => _ItemName;
        set {
            _ItemName = value;
            OnPropertyChanged("ItemName");
        }
    }

    public string ItemImage
    {
        get => _ItemImage;
        set {
            _ItemImage = value;
            OnPropertyChanged("ItemImage");
        }
    }

    //
    // Events
    //

    //Declare the event
    public event PropertyChangedEventHandler PropertyChanged;

    // Create the OnPropertyChanged method to raise the event
    // The calling member's name will be used as the parameter.
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

正如您从第一张图片中看到的那样工作正常,问题在于绑定命令以更改“内容区域”(白色区域第一张图片)。

我将尝试在此处描述内容区域的代码:在 MainWindow.xaml(第一个代码片段)中,您将看到一个“ContentControl”,在这里我绑定了 CurrentViewModel,它表示要在内容区域中显示的内容。 我使用 navigationStore 来存储必须显示的 ViewModel。

导航商店.cs:

public class NavigationStore
{
    public BaseViewModel CurrentViewModel { get; set; }
}

以及“内容页面”的示例:查看:

<UserControl x:Class="TSD.Views.UserControlHcfContent"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:TSD.Views"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>

    <StackPanel Orientation="Horizontal">
        <TextBlock Margin="10,0,0,0" Text="Search:"></TextBlock>
        <TextBox Margin="10,0,0,0" Width="150"></TextBox>
        <Button Margin="50,0,0,0" Padding="10,0,10,0" Content="Create new system"></Button>
        <Button Margin="50,0,0,0" Padding="10,0,10,0" Content="Edit existing system"></Button>
    </StackPanel>
    
    <Grid Grid.Row="1">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
                    
    </Grid>

    </Grid>

视图模型:

class HcfContentViewModel:BaseViewModel
{
    public HcfContentViewModel()
    {
        //Check here for available systems and create model accordingly.
        _hcfContent = new HcfContentModel();
    }

    private HcfContentModel _hcfContent;

    public HcfContentModel hcfContent
    {
        get { return _hcfContent; }
    }

}

Model:

class HcfContentModel : INotifyPropertyChanged
{

    private string _activeProductName;
    private int _availableProducts;
    
    public HcfContentModel()
    {

    }

    public string activeProductName
    {
        get => _activeProductName;
        set 
        {
            _activeProductName = value;
            OnPropertyChanged("activeProductName");
        }
    }

    public int availableProducts
    {
        get => _availableProducts;
        set 
        {
            _availableProducts = value;
            OnPropertyChanged("availableProducts");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

问题陈述:

在导航菜单中按下/单击 ItemMenu 时,不会触发该命令。 我正在尝试执行 ShowHcfCommand 以将内容区域更改为 HcfViewModel。

ShowHcf 命令:

class ShowHcfCommand : BaseCommand
{
    private readonly NavigationStore _navigationStore;

    public ShowHcfCommand(NavigationStore navigationStore)
    {
        _navigationStore = navigationStore;
    }

    public override void Execute(object parameter)
    {
        _navigationStore.CurrentViewModel = new HcfContentViewModel();
        Console.WriteLine("Command Executed!");
    }
}

我希望提供的信息足够清晰,如果没有,请告诉我,以便我更新。 完整的项目可以从我的 github 页面克隆:

https://github.com/SaCam/FirstApp

我正在使用VS2017。

如果有关于如何改进一般项目布局的提示,请告诉我。

提前致谢。 亲切的问候,山姆

在 MainWindowViewModel.cs 你永远不会实例化你的命令的实例

public ICommand navigateHcfCommand; //not instantiated

但在构造函数中使用:

       UserControlMenuItem Item3 = new UserControlMenuItem(navigateHcfCommand, "HOSE CONNECTION FINDER", "/assets/hose_icon.png");

菜单项绑定到 null 命令。 如果您更改它仍然不会显示正确的内容视图模型,因为您没有调用属性更改或导航存储 class 中的任何其他事件

首先,感谢您的帮助。

在遵循评论 op T.Schwarz 之后,我设法让它工作。

我更新了 MainWindowViewModel.cs 以修复我分配的 null 命令。 这最初解决了命令未执行的问题,但正如 T.Schwarz 所提到的,它仍然不会改变视图。 我必须在 MainWindowViewModel 中通知 Property Changed,而不是从 navigationStore 通知。 更新后的 MainWindowViewModel 如下所示:

class MainWindowViewModel : BaseViewModel, INotifyPropertyChanged
{

    // Class fields
    private string _MainWindowText;
    private List<UserControlMenuItem> _MenuItems;
    private string _ImagePath;
    private readonly NavigationStore _navigationStore;

    // Commands
    public ICommand navigateHcfCommand;
    public ICommand dummyCommand;


    public MainWindowViewModel(NavigationStore navigationStore)
    {
        //Stores
        _navigationStore = navigationStore;

        //Commands used in this view
        dummyCommand = new DummyCommand();
        navigateHcfCommand = new ShowHcfCommand(this);

        //Set name for window.
        _MainWindowText = "My first MVVM application";
        _ImagePath = "/assets/placeholder_image_name.png";

        //Create MenuItems
        UserControlMenuItem Item1 = new UserControlMenuItem(dummyCommand, "ISSUE LIST", "/assets/issue_list_icon.png");
        UserControlMenuItem Item2 = new UserControlMenuItem(dummyCommand, "SYSTEM SHORTLOG DOWNLOAD","/assets/sld_icon.jpg");
        UserControlMenuItem Item3 = new UserControlMenuItem(navigateHcfCommand, "HOSE CONNECTION FINDER", "/assets/hose_icon.png");
        UserControlMenuItem Item4 = new UserControlMenuItem(dummyCommand, "GET CONFIG", "/assets/config_icon.png");
        UserControlMenuItem Item5 = new UserControlMenuItem(dummyCommand, "KNOWLEDGE TRANSFER", "/assets/knowledge_transfer_icon.png");
        UserControlMenuItem Item6 = new UserControlMenuItem(dummyCommand, "AMBITION LISTS", "/assets/ambition_list_icon.png");
        
        //Properties
        _MenuItems = new List<UserControlMenuItem> { Item1, Item2, Item3, Item4, Item5, Item6 };
    }


    //Class properties
    public string ImagePath
    {
        get => _ImagePath;
        set {
            _ImagePath = ImagePath;
            OnPropertyChanged("ImagePath");
        }
    }

    public string MainWindowText
    {
        get => _MainWindowText;
        set {
            _MainWindowText = value;
            OnPropertyChanged("MainWindowText");
        }
    }

    public List<UserControlMenuItem> MenuItems
    {
        get => _MenuItems;
        set {
            _MenuItems = value;
            OnPropertyChanged("MenuItems");
        }
    }

    public BaseViewModel CurrentViewModel
    {
        get => _navigationStore.CurrentViewModel;
        set {
            _navigationStore.CurrentViewModel = value;
            OnPropertyChanged("CurrentViewModel");
        }
    }

    public ICommand UpdateCommand
    {
        get;
        private set;
    }

    //
    // Events
    //

    //Declare the event
    public event PropertyChangedEventHandler PropertyChanged;

    // Create the OnPropertyChanged method to raise the event
    // The calling member's name will be used as the parameter.
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}// Class

从上面的代码可以看出,我现在将 MainWindowViewModel 传递给 ShowHcfCommand,在此命令中,我更改了 MainWindowViewModel 中 CurrentViewModel 的值。

它现在正在工作,但是我不确定这是否是最有效的解决方案,或者它是否符合“通用”MVVM 规则。 如果有人有关于如何改进结构的提示,请告诉我。

亲切的问候,山姆

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM