简体   繁体   English

xaml 中的命令绑定与 MVVM

[英]Command Binding in xaml with MVVM

Currently I'm trying to create my first Windows Application using C#, WPF, MVVM, see picture below.目前我正在尝试使用 C#、WPF、MVVM 创建我的第一个 Windows 应用程序,见下图。

主窗口

My File structure:我的文件结构:

文件结构

The goal of the application is to have a navigation menu on the left hand side, which can be used to navigate to different Content which has to be displayed in the white area (see picture above).该应用程序的目标是在左侧有一个导航菜单,可用于导航到必须在白色区域显示的不同内容(见上图)。

The "MenuItems" (6 buttons on the left hand side) in the navigation menu are used to change the content show in the white area.导航菜单中的“MenuItems”(左侧6个按钮)用于更改白色区域中显示的内容。 I create these buttons using the following code:我使用以下代码创建这些按钮:

in my MainWindow.xaml I use an "ItemsControl" binding the "ItemScource" to a list of MenuItems.在我的 MainWindow.xaml 中,我使用“ItemsControl”将“ItemSource”绑定到 MenuItems 列表。

MainWindows.xaml: 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>

I use my code behind to set the MainWindowViewModel as DataContext.我使用后面的代码将 MainWindowViewModel 设置为 DataContext。

MainWindow.xaml.cs: 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

In my MainWindowViewModel I create a list of UserControls, each UserControl representing a button in the navigation menu, this list is called _menuItems and is binded to the ItemsControl show earlier.在我的 MainWindowViewModel 中,我创建了一个 UserControls 列表,每个 UserControl 代表导航菜单中的一个按钮,这个列表称为 _menuItems 并绑定到前面显示的 ItemsControl。

MainWindowViewModel.cs: 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 ( To define layout of the buttons ) this UserControl is added to the list _menuItems in MainWindowViewModel: 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>

The UserControlMenuItem has a MenuItemViewModel.cs containing th following code: 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

and the MenuItemModel.cs:和 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));
        }
    }
}

As you can see from the first picture this is working fine, the issue is within binding the command to change the "content area" (White area first picture).正如您从第一张图片中看到的那样工作正常,问题在于绑定命令以更改“内容区域”(白色区域第一张图片)。

I'll shortly try to describe the code for the content area here: In MainWindow.xaml (first code snippet) you'll see a "ContentControl", here I bind the CurrentViewModel, which represent content to be displayed in the content area.我将尝试在此处描述内容区域的代码:在 MainWindow.xaml(第一个代码片段)中,您将看到一个“ContentControl”,在这里我绑定了 CurrentViewModel,它表示要在内容区域中显示的内容。 I use a navigationStore to store the ViewModel which has to be displayed.我使用 navigationStore 来存储必须显示的 ViewModel。

navigationStore.cs:导航商店.cs:

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

and an example of a "content page": View:以及“内容页面”的示例:查看:

<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>

ViewModel:视图模型:

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: 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));
        }
    }
}

Problem statement:问题陈述:

When pressing/Clicking an ItemMenu in the navigation menu the command is not triggerd.在导航菜单中按下/单击 ItemMenu 时,不会触发该命令。 I'm trying to execute the ShowHcfCommand to change the content area to the HcfViewModel.我正在尝试执行 ShowHcfCommand 以将内容区域更改为 HcfViewModel。

ShowHcfCommand: 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!");
    }
}

I hope the information provided is sufficient and clear, if not please let know so I can update it.我希望提供的信息足够清晰,如果没有,请告诉我,以便我更新。 The complete project can be cloned from my github page:完整的项目可以从我的 github 页面克隆:

https://github.com/SaCam/FirstApp https://github.com/SaCam/FirstApp

I'm using VS2017.我正在使用VS2017。

If there are tips on how to improve the general project layout please let me know.如果有关于如何改进一般项目布局的提示,请告诉我。

Thanks in advance.提前致谢。 Kind regards, Sam亲切的问候,山姆

in MainWindowViewModel.cs you never instantiate an instance of your command在 MainWindowViewModel.cs 你永远不会实例化你的命令的实例

public ICommand navigateHcfCommand; //not instantiated

but used in the Constructor:但在构造函数中使用:

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

The Menu Item is bound to a null command.菜单项绑定到 null 命令。 If you change that it'll still not show the correct contentviewmodel because you don't call property changed or any other event from the navigationstore class如果您更改它仍然不会显示正确的内容视图模型,因为您没有调用属性更改或导航存储 class 中的任何其他事件

First of all, Thanks for the help.首先,感谢您的帮助。

After following the comment op T.Schwarz I managed to get it working.在遵循评论 op T.Schwarz 之后,我设法让它工作。

I updated my MainWindowViewModel.cs to fix the null command I was assigning.我更新了 MainWindowViewModel.cs 以修复我分配的 null 命令。 This initially fixed the issue of the command not being executed, but as mentiond by T.Schwarz it would still not change the view.这最初解决了命令未执行的问题,但正如 T.Schwarz 所提到的,它仍然不会改变视图。 I had to notify Property Changed within my MainWindowViewModel and not from the navigationStore.我必须在 MainWindowViewModel 中通知 Property Changed,而不是从 navigationStore 通知。 the updated MainWindowViewModel is shown below.:更新后的 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

As you can see from the code above, I now pass my MainWindowViewModel to the ShowHcfCommand, within this command I change the value of CurrentViewModel in the MainWindowViewModel.从上面的代码可以看出,我现在将 MainWindowViewModel 传递给 ShowHcfCommand,在此命令中,我更改了 MainWindowViewModel 中 CurrentViewModel 的值。

It is working now, however I'm not sure if this is the most efficient solution or if it is inline with "general" MVVM rules.它现在正在工作,但是我不确定这是否是最有效的解决方案,或者它是否符合“通用”MVVM 规则。 If anyone has tips on how to improve the structure please let me know.如果有人有关于如何改进结构的提示,请告诉我。

Kind regards, Sam亲切的问候,山姆

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

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