WPF MVVM 案例:ItemsControl 包含用于更新 ViewModel 中的属性的超链接和命令

本文关键字:属性 超链接 命令 ViewModel 更新 案例 MVVM ItemsControl 用于 包含 WPF | 更新日期: 2023-09-27 18:33:36

我有一个案例,有点复杂,但我尝试说明并进行一些修改以简单的方式显示这一点。

假设我有一个 Window1 作为视图,一个 Window1ViewModel 作为其视图模型。我还有一个SubMenuViewModel类来表示窗口中的子菜单。我想在 Window1 中创建一个包含许多子菜单的项目控件。每次用户单击其中一个子菜单时,属性 CurrentSubMenu 都会更新为相应的子菜单。这就是问题所在,我无法在 Window1ViewModel 中更新当前子菜单。

.

子菜单视图模型 :

public class SubMenuViewModel : INPC
    {
        private string _submenuname;
        public string SubMenuName 
        {
            get
            {   return _submenuname;   }
            set
            {
                _submenuname = value;
                RaisePropertyChanged("SubMenuName");
            }
        }
        private string _displayname;
        public string DisplayName 
        {
            get
            {   return _displayname;   }
            set
            {
                _displayname = value;
                RaisePropertyChanged("DisplayName");
            }
        }
        // Command for Hyperlink in ItemsControl //
        private RelayCommand _commandSubmenu;
        public RelayCommand CommandSubMenu 
        {
            get
            {   return _commandSubmenu;   }
            set 
            {
                _commandSubmenu = value;
                RaisePropertyChanged("CommandSubMenu");
            }
        }
        // end of command
        public SubMenuViewModel(string Submenu_name, string Display_name)
        {
            _submenuname = Submenu_name;
            _displayname = Display_name;
        }
    }

窗口 1 :

<Window x:Class="SomeProject.Window1"
        xmlns:vm="clr-namespace:SomeProject.ViewModel"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="This is Some Project" WindowState="Maximized">
    <Window.DataContext>
        <vm:Window1ViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="250" />
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Border Name="SubMenuPanelBorder"
                Grid.Column="0"
                Grid.Row="1"
                HorizontalAlignment="Stretch"
                VerticalAlignment="Stretch"
                Margin="15 0 15 0"
                BorderThickness="2"
                BorderBrush="Black"
                Padding="10 10">
            <HeaderedContentControl Content="{Binding Path=SubMenus}" Header="Panel Submenu :">
                <HeaderedContentControl.ContentTemplate>
                    <DataTemplate>
                        <ItemsControl ItemsSource="{Binding}" Foreground="White" Background="Transparent" Margin="0 15">
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <TextBlock>
                                        <Hyperlink Command="{Binding Path=CommandSubMenu}">
                                            <TextBlock Margin="0 5" Text="{Binding Path=DisplayName}"/>
                                        </Hyperlink>
                                    </TextBlock>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>
                    </DataTemplate>
                </HeaderedContentControl.ContentTemplate>
            </HeaderedContentControl>
        </Border>
        .......
        .......
    </Grid>
</Window>

窗口1视图模型 :

class Window1ViewModel : INPC
    {
        private List<SubMenuViewModel> _submenus;
        public List<SubMenuViewModel> SubMenus 
        {
            get
            {   return _submenus;   }
            set
            {
                _submenus = value;
                RaisePropertyChanged("SubMenus");
            }
        }
        private SubMenuViewModel _currentSubMenu;
        public SubMenuViewModel CurrentSubMenu
        {
            get
            {   return _currentSubMenu;    }
            set
            {
                _currentSubMenu = value;
                RaisePropertyChanged("CurrentSubMenu");
            }
        }
        public Window1ViewModel()
        {
            SubMenus = MenuDefinition.GetSubMenus();
            /*** 
            Set the SubMenus's command to update the CurrentSubMenu,
            and == HERE IS WHERE I GOT LOST ==. 
            The CurrentSubMenu is always point to the last submenu in the loop when clicked. 
            By the way, the reason I use loop is because the submenu in the Menu definition sometimes changed and there are many number of submenus there, and there are some other reasons (I use Menu-submenu mechanism, but it's not essential to show it here because it's not the main point I want to show). So, finally I use loop instead of specifying each command one by one.  
            ***/
            foreach(SubMenuViewModel submenu in SubMenus){
                submenu.CommandSubMenu=new RelayCommand(()=>clickedSubMenu(submenu.SubMenuName));
            }
        }
        public void clickedSubMenu(string submenu_name)
        {
            CurrentSubMenu = SubMenus.Find(sub => sub.SubMenuName == submenu_name);
        }
    }

菜单定义 :

public static List<SubMenuViewModel> GetSubMenus()
        {
            return new List<SubMenuViewModel>
                    {
                        new SubMenuViewModel("overview_product", "Overview Products"),
                        new SubMenuViewModel("search_product","Search Products"),
                        new SubMenuViewModel("update_product","Update Data Products"),
                        new SubMenuViewModel("order_product","Order Products"),
                        new SubMenuViewModel("supplier_product","Products Supplier"),
                        .................
                        .................
                        .................
                    };                        
            }
        }

WPF MVVM 案例:ItemsControl 包含用于更新 ViewModel 中的属性的超链接和命令

我相信

你的绑定属性应该这样写。

private string _Terminal;
    public string Terminal
    {
        get
        {
            return _Terminal;
        }
        set
        {
            _Terminal = value;
            OnPropertyChanged("Terminal");
        }
    }

确保视图模型实现 INotifyPropertyChanged

public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
        if (propertyChanged != null)
        {
            PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
            propertyChanged(this, e);
        }
    }

解决方案很简单,但不知何故,我需要一点时间才能找到这个。在将它传递给 RelayCommand 的委托操作之前,我必须添加一个临时变量。这样,RelayCommand 的委托"操作执行"将采用正确的参数并指向正确的子菜单。当然,并不总是最后一个子菜单。

返回窗口1视图模型

因此,而不是直接使用子菜单。子菜单名称属性,如下所示

// This way, when clicked, the command will always point to the last submenu from the loop
public Window1ViewModel()
        {
            SubMenus = MenuDefinition.GetSubMenus(); 
            foreach(SubMenuViewModel submenu in SubMenus){
                submenu.CommandSubMenu=new RelayCommand(()=>clickedSubMenu(submenu.SubMenuName));
            }
        }

我们必须添加一些这样的临时变量。

// This way, when clicked, the command will point to the correct submenu
public Window1ViewModel()
        {
            SubMenus = MenuDefinition.GetSubMenus();
            foreach(SubMenuViewModel submenu in SubMenus){
                string Temp=submenu.SubMenuName
                submenu.CommandSubMenu=new RelayCommand(()=>clickedSubMenu(Temp));
            }
        }

并且输出会有所不同。