【问题标题】:WPF MVVM: Delete Items from listbox in an itemscontrolWPF MVVM:从项目控件中的列表框中删除项目
【发布时间】:2019-10-10 05:51:52
【问题描述】:

我有一个如下所示的 TableModel:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;

namespace TestPRL.Model
{
    public class TableModel : INotifyPropertyChanged
    {

        public TableModel()
        {
            _Players = new ObservableCollection<Player>();
        }

        private string _Name;

        public string Name
        {
            get { return _Name; }
            set {

                if (_Name!=value)
                {
                    _Name = value;
                    RaisePropertyChanged("Name");
                }
            }
        }

        private string _Game;

        public string Game
        {
            get { return _Game; }
            set
            {
                if (_Game != value)
                {
                    _Game = value;
                    RaisePropertyChanged("Game");
                }
            }
        }



        private ObservableCollection<Player> _Players;

        public ObservableCollection<Player> Players
        {
            get { return _Players; }
            set {

                if (_Players!=value)
                {
                    _Players = value;
                    RaisePropertyChanged("Players");
                }
            }
        }

        public void DeletePlayer()
        {
            _Players.Remove(_SelectedPlayer);
            RaisePropertyChanged("Players");
        }

        private Player _SelectedPlayer;

        public Player SelectedPlayer
        {
            get
            {
                return _SelectedPlayer;
            }

            set
            {
                if (_SelectedPlayer != value)
                {
                    _SelectedPlayer = value;
                    Trace.WriteLine(_SelectedPlayer.Name);
                    RaisePropertyChanged("SelectedPlayer");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }
    public class Player : INotifyPropertyChanged
    {

        public enum StateEnum
        {
            Normal,
            Waiting,
            Playing,
            Absent
        }

        private int _ID;

        public int ID
        {
            get { return _ID; }
            set
            {
                if (_ID != value)
                {
                    _ID = value;
                    RaisePropertyChanged("ID");
                }
            }
        }

        private string _Name;

        public string Name
        {
            get { return _Name; }
            set
            {

                if (_Name != value)
                {
                    _Name = value;
                    RaisePropertyChanged("Name");
                }
            }
        }

        private StateEnum _State;

        public StateEnum State
        {
            get { return _State; }
            set
            {
                if (_State != value)
                {
                    _State = value;
                    RaisePropertyChanged("State");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }
}

然后查看模型

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
using TestPRL.Model;
using TestPRL.Classes;
using TestPRL.View;

namespace TestPRL.ViewModel
{
    public class TableViewModel:INotifyPropertyChanged
    {

        public TableViewModel()
        {
            MyCommand = new RelayCommand(new Action<object>(MyCommand_Execute));
            Tables = new ObservableCollection<TableModel>();
        }

        public ObservableCollection<TableModel> Tables { get; set; }

        public void LoadTables()
        {
            TableModel TableModel1 = new TableModel();
            TableModel1.Name = "1";
            TableModel1.Game = "Game1";
            TableModel1.Players.Add(new Player { ID = 1, Name = "Tim", State = Player.StateEnum.Normal });
            TableModel1.Players.Add(new Player { ID = 2, Name = "Tom", State = Player.StateEnum.Absent });
            TableModel1.Players.Add(new Player { ID = 3, Name = "Reese", State = Player.StateEnum.Playing });
            TableModel1.Players.Add(new Player { ID = 4, Name = "Paula", State = Player.StateEnum.Normal });

            TableModel TableModel2 = new TableModel();
            TableModel2.Name = "2";
            TableModel2.Game = "Game2";
            TableModel2.Players.Add(new Player { ID = 1, Name = "Karen", State = Player.StateEnum.Normal });
            TableModel2.Players.Add(new Player { ID = 2, Name = "Summer", State = Player.StateEnum.Playing });
            TableModel2.Players.Add(new Player { ID = 3, Name = "Willy", State = Player.StateEnum.Absent });
            TableModel2.Players.Add(new Player { ID = 4, Name = "Mike", State = Player.StateEnum.Waiting });
            TableModel2.Players.Add(new Player { ID = 4, Name = "Peter", State = Player.StateEnum.Waiting });

            Tables.Add(TableModel1);
            Tables.Add(TableModel2);
        }


        private ICommand _MyCommand;

        public ICommand MyCommand
        {
            get { return _MyCommand; }
            set { _MyCommand = value;  }
        }

        private void MyCommand_Execute(object sender)
        {
            var myView = sender as TableView;

            myView?.Test();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }
}

和视图:

<UserControl x:Class="TestPRL.View.TableView"
             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:rootns="clr-namespace:TestPRL"
             xmlns:local="clr-namespace:TestPRL.View"
             xmlns:classes="clr-namespace:TestPRL.Classes"
             xmlns:viewmodel="clr-namespace:TestPRL.ViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid Name="TablesGrid">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <StackPanel HorizontalAlignment="Left">
            <ItemsControl ItemsSource="{Binding Tables}" x:Name="MyTables">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Horizontal"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Border BorderBrush="Black" BorderThickness="0" Margin="5">
                            <StackPanel Orientation="Vertical" Margin="6">
                                <Label Content="{Binding Name}"  Foreground="Black" FontSize="16" FontWeight="Bold" Background="Transparent" BorderThickness="0"/>
                                <ListBox Name="PlayerListbox" ItemsSource="{Binding Players}" SelectedItem="{Binding DataContext.SelectedPlayer, RelativeSource={RelativeSource AncestorType=UserControl}}" IsSynchronizedWithCurrentItem="True" Background="#33A2A2A2" Foreground="White" BorderThickness="0" FontWeight="Bold" FontSize="12" Margin="0,15,0,0">
                                    <ListBox.ItemTemplate>
                                        <DataTemplate>
                                            <StackPanel Orientation="Horizontal">
                                                <TextBlock Name = "TextControl" Text="{Binding Name}" Background="Transparent" Padding="4">
                                                    <TextBlock.Style>
                                                        <Style TargetType = "TextBlock" >
                                                            <Style.Triggers >
                                                                <DataTrigger Binding="{Binding State}" Value="Playing">
                                                                    <Setter Property = "Foreground" Value="Blue"/>
                                                                </DataTrigger>
                                                                <DataTrigger Binding="{Binding State}" Value="Waiting">
                                                                    <Setter Property = "Foreground" Value="Gray"/>
                                                                </DataTrigger>
                                                                <DataTrigger Binding="{Binding State}" Value="Absent">
                                                                    <Setter Property = "Foreground" Value="#FF0097FF"/>
                                                                </DataTrigger>
                                                            </Style.Triggers>
                                                        </Style>
                                                    </TextBlock.Style>
                                                </TextBlock>
                                            </StackPanel>
                                        </DataTemplate>
                                    </ListBox.ItemTemplate>
                                    <ListBox.ContextMenu>
                                        <ContextMenu DataContext="{Binding Path=DataContext, Source={x:Reference TablesGrid}}">
                                            <MenuItem Header="Delete player" Command="{Binding MyCommand}" CommandParameter="{Binding}"/>
                                            <MenuItem Header="Set player state to playing"/>
                                            <MenuItem Header="Set player state to absent"/>
                                        </ContextMenu>
                                    </ListBox.ContextMenu>
                                </ListBox>
                            </StackPanel>
                        </Border>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </StackPanel>
    </Grid>
</UserControl>

MainWindow.xaml:

<Window x:Class="TestPRL.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:TestPRL"
        xmlns:view = "clr-namespace:TestPRL.View"
        xmlns:viewmodel ="clr-namespace:TestPRL.ViewModel"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
    </Window.Resources>
    <Grid>
        <view:TableView x:Name="TableViewControl" Loaded="TableViewControl_Loaded"/>
    </Grid>
</Window>

还有我设置数据上下文的 MainWindow.xaml.cs:

using System.Windows;

namespace TestPRL
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void TableViewControl_Loaded(object sender, RoutedEventArgs e)
        {
            TestPRL.ViewModel.TableViewModel TableViewModel = new ViewModel.TableViewModel();
            TableViewControl.DataContext = TableViewModel;
            TableViewModel.LoadTables();           
        }
    }
}

项目控件显示我想要的所有数据,但我不知道如何使用上下文菜单更改/删除选定的播放器。

如何获得正确的数据上下文(表格和属性 Players)来编辑或删除播放器?

有人对我有正确的想法吗?

提前致谢, 最好的问候,

弗洛

【问题讨论】:

  • 绑定到视图中的命令Command="{Binding DeleteCommand}"。在您的ViewModel 中从您的列表中删除所选项目。看看这个答案的 ViewModel 部分stackoverflow.com/a/40101345/2289942
  • 感谢您的回答。我像在帖子中写的那样实现了这个,但是如果我点击上下文菜单,我会得到一个数据错误,我想,我有错误的数据上下文,但在这种情况下,我不知道如何改变它...... System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”“PokerTable”(HashCode=2679067)上找不到“MyCommand”属性。绑定表达式:路径=我的命令; DataItem='表' (HashCode=2679067);目标元素是'MenuItem'(名称='');目标属性是“Command”(输入“ICommand”)
  • 很抱歉,但这也行不通,似乎绑定找不到用户控件,我不知道为什么:System.Windows.Data 错误:4:无法通过引用“RelativeSource FindAncestor,AncestorType='System.Windows.Controls.UserControl',AncestorLevel='1'”找到绑定源。 BindingExpression:Path=DataContext.MyCommand;数据项=空;目标元素是'MenuItem'(名称='');目标属性是“Command”(输入“ICommand”)
  • 感谢您的回答,但错误仍然存​​在......也许是因为上下文菜单不在可视树中?
  • 我已经编辑了问题并添加了所需的信息。非常感谢!

标签: wpf binding


【解决方案1】:

我试过你的代码。

确实,因为ContextMenu 不在同一个VisualTree FindAncestor 将不起作用。

但这会起作用:

<ListBox.ContextMenu>
   <ContextMenu DataContext="{Binding Path=DataContext, Source={x:Reference TablesGrid}}">
        <MenuItem Header="Delete player" Command="{Binding Path=MyCommand}"/>
        <MenuItem Header="Set player state to playing"/>
        <MenuItem Header="Set player state to absent"/>
   </ContextMenu>
</ListBox.ContextMenu>

编辑:

我不确定要求是什么,但是,不要过多地修改您的代码并让您了解您可以做什么:

  1. 为每个Table 创建一个ViewModel,并在VM 上保留SelectedPlayer 等属性,当然还有Commands

  2. Player 中创建一个TableId 属性,这样您就可以知道玩家在哪个表上玩。这意味着Player 一次只能播放一个Table

第二种方法的示例代码:

播放器类:

public class Player : INotifyPropertyChanged
{
   private int? _playingOnTableId;

   public int? PlayingOnTableId
   {
       get => _playingOnTableId;
       set
       {
          if(_playingOnTableId == value) return;
          _playingOnTableId = value;
          OnPropertyChanged(nameof(PlayingOnTableId);
       }    
   }
}

表格类:

public class TableModel : INotifyPropertyChanged
{
    private int? _tableId;

    public int? TableId
    {
       get => _tableId;
       set
       {
           if(_tableId == value) return;
           _tableId = value;
           OnPropertyChanged(nameof(TableId);
        }    
    }
    public void DeletePlayer(Player playerToDelete)
    {
        Players.Remove(playerToDelete);
        playerToDelete.PlayingOnTableId = null;
    } 

    public void AddPlayer(Player playerToDelete)
    {
        Players.Add(playerToDelete);
        playerToDelete.PlayingOnTableId = TableId;
        //YES, You need to give your tables unique ID
    } 
}

注意:您需要为您的表格提供唯一 ID!

从您的 TableViewModel 中,您可以使用这些方法来添加和删除玩家:

添加:

TableModel1.AddPlayer(new Player { ID = 1, Name = "Tim", State = Player.StateEnum.Normal });

删除:

Tables.FirstOrDefault(t => TableId == SelectedPlayer.PlayingOnTableId)?.DeletePlayer(SelectedPlayer);

这是基础,如果玩家是其中的一部分,您必须确保将其从其他表中删除。

【讨论】:

  • 现在我有了正确的数据上下文,非常感谢!最后一个问题:使用 SelectedPlayer.State 我可以设置所选播放器的状态,效果很好。但是如何从列表中删除播放器?我只知道选定的播放器,而不是存在“Players”属性的对象“TableModel”。我怎样才能实现 Observablecollection “Players”,我可以在哪里执行删除?
  • @FloSu 好吧...基本上你的方法是错误的,你根本不知道属于哪个赌桌玩家。我会为每张桌子设置SelectedPlayer。删除将是Players.Remove(SelectedPlayer);
  • 我再次编辑了上面的代码,SelectedPlayer 现在绑定到表格的 SelectedPlayer 属性上,还有一个新的 void 可以移除播放器。但是我怎样才能告诉 MyCommand 执行表模型的 void RemovePlayer 呢?我有点困惑。
  • 谢谢,现在它就像一个魅力。我不知道,如果这是最好的方法,我想要的只是将一些表添加到列表中并与玩家一起显示表。在视图中,我想添加/删除表格,并编辑播放器(添加/删除/重命名/设置新状态)。有更好的方法吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-07
  • 2010-11-29
  • 1970-01-01
  • 2011-03-25
相关资源
最近更新 更多