[英]Command binding not working - MVVM Light RelayCommand and XAML issues
[英]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.