0

我希望我的 MVVM 表单中的组合框在绑定数据更改后立即刷新显示当前所选项目的选择框和当前所选项目的下拉列表项。我不能让这发生。刷新图片也很重要。

示例表单有 2 个显示预加载人员的组合框和一个用于添加人员的按钮和一个用于更改现有人员的某些数据的按钮。单击该按钮时,该Person.Type字段会随机更改一些,并且将不同的图片文件字符串存储在Person.PicFullPath.

<Window x:Class="combobox_test__01.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:combobox_test__01"
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="420" WindowStartupLocation="CenterScreen">
    <Window.Resources>
        <local:VM x:Key="vm" />

        <DataTemplate x:Key="cboTemplate" >
            <StackPanel Orientation="Horizontal">
                <Border Height="80" Width="80" >
                    <Image>
                        <Image.Source>
                            <PriorityBinding>
                                <Binding Path="PicFullPath"  Mode="TwoWay"/>
                            </PriorityBinding>
                        </Image.Source>
                    </Image>
                </Border>
                <Canvas Height="80" Width="160" >
                    <StackPanel>
                        <TextBlock HorizontalAlignment="Left" Height="16" Text="{Binding Path=Name, Mode=TwoWay}"  />
                        <TextBlock HorizontalAlignment="Left" Height="16" Text="{Binding Path=Type, Mode=TwoWay}"  />
                    </StackPanel>
                </Canvas>
            </StackPanel>
        </DataTemplate>

    </Window.Resources>
    <Window.DataContext>
        <Binding Source="{StaticResource vm}" />
    </Window.DataContext>
    <Grid>
        <ComboBox Name="cbo1" IsSynchronizedWithCurrentItem="True" HorizontalAlignment="Left" Margin="19,11,0,0" VerticalAlignment="Top" Width="159" 
                ItemsSource="{Binding People}" 
                SelectedItem="{Binding SelectedPerson}"  
                DisplayMemberPath="Type" />
        <Button Content="New Row" HorizontalAlignment="Left" Margin="224,11,0,0" VerticalAlignment="Top" Width="142" 
                Command="{Binding NewRowCommand}" />
        <Button Content="Change current row data" HorizontalAlignment="Left" Margin="224,48,0,0" VerticalAlignment="Top" Width="142" 
                Command="{Binding AlterRowCommand}" />
        <ComboBox Name="cbo2" IsSynchronizedWithCurrentItem="True" HorizontalAlignment="Left" Margin="19,130,0,0" VerticalAlignment="Top" Width="159" Height="82" 
            ItemsSource="{Binding People}"
            SelectedItem="{Binding SelectedPerson}" 
            ItemTemplate="{StaticResource cboTemplate}" />
    </Grid>
</Window>

using myAssemblies;
using System;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System.ComponentModel;

namespace combobox_test__01
{
    public class VM : INotifyPropertyChanged
    {
        private ObservableCollection<Person> people;
        public ObservableCollection<Person> People
        {
            get { return people; }
            set
            {
                 people = value;
                OnPropertyChanged("People");
            }
        }

        private Person selectedPerson;
        public Person SelectedPerson
        {
            get { return selectedPerson; }
            set
            {
                selectedPerson = value;
                OnPropertyChanged("SelectedPerson");
                People = People;
            }
        }

        private int idx = 0;
        private string[,] newP;
        private string DefaultPic = @"D:\Visual Studio Testing\Icons\user-icon.png",
            NewPic = @"D:\Visual Studio Testing\Small size pictures\mugshot";
        private Random random = new Random();

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

        public VM()
        {
            People = new ObservableCollection<Person>()
            {
                new Person() {Name="Aa", Type="Taa", PicFullPath=DefaultPic},
                new Person() {Name="Bb", Type="Tbb", PicFullPath=DefaultPic},
                new Person() {Name="Cc", Type="Tcc", PicFullPath=DefaultPic}
            };

            newP = new string[4, 3] { { "Dd", "Tdd", "" }, { "Ee", "Tee", "" }, { "Ff", "Tff", "" }, { "Gg", "Tgg", "" } };
        }

        public ICommand AlterRowCommand => new RelayCommandBase(AlterRow);
        private void AlterRow(object parameter)
        {
            //Make SelectedPerson.Type into a string ending with 2 randomish digits
            string fmts, picChoice;
            GetDifferentData(out fmts, out picChoice);
            string s = SelectedPerson.Type.Substring(0,3).PadRight(5);
            SelectedPerson.Type = s + fmts;

            // Use those 2 randomish digits to choose a picture from existing ones
            SelectedPerson.PicFullPath = NewPic + picChoice;

            // Force both setters to execute
            SelectedPerson = SelectedPerson;
        }

        public ICommand NewRowCommand => new RelayCommandBase(NewRow);
        private void NewRow(object parameter)
        {
            string fmts, picChoice;
            GetDifferentData(out fmts, out picChoice);
            People.Add(new Person() { Name = newP[idx, 0], Type = newP[idx++, 1], PicFullPath = NewPic + picChoice });
        }

        private void GetDifferentData(out string fmts, out string picChoice)
        {
            int randomi = random.Next(1, 35);
            fmts = randomi.ToString("D2");
            picChoice = fmts + ".jpg";
        }
    }

    public class Person
    {
        public string Name { get; set; }
        public string Type { get; set; }
        public string PicFullPath { get; set; }
    }
}

这是行为:

运行应用程序
视图模型处于状态 1
单击“更改当前行数据”
视图模型处于状态 2
没有可见变化:我希望选择框显示更改的Type数据和不同的图片

点击组合框 cbo1
下拉列表显示People.Type. 选择框仍然显示旧数据。

点击组合框cbo2
下拉列表显示变化People.Type和不同的图片。选择框仍然显示旧数据。

再次单击“更改当前行数据”而不移动所选项目。

视图模型处于状态 3。
没有可见的变化:随着下拉列表收回,表单看起来与初始化时相同。

点击组合框 cbo1
下拉列表仍显示第一次更改的People.Type,但People.Type已再次更改。选择框仍然显示原始数据。

单击组合框 cbo2
下拉列表自上次删除后未更改。选择框仍然显示原始数据。

视图模型中的状态发生了 3 次更改,但组合框在选择框中显示状态 1,在下拉列表中显示状态 2。我希望他们在两个地方都显示状态 3。

单击组合框 cbo1 并选择另一个项目,然后选择第一个项目。所以我们已经移动了选定的项目并将其移回。
两个组合框都在选择框中显示最新数据,
下拉列表已过时。两者都显示状态 2,因此更改选择并将其更改回并没有更改下拉列表中显示的数据。

单击“新行”
视图模型处于状态 4
无可见变化:正如预期的那样 - 未选择新人员,因此不会有可见变化。

单击组合框
下拉列表中显示新人,但第一项仍显示状态 2。

没有点击将使下拉列表在第一次更改后显示更改 - 它们卡在显示状态 2。

如何使选择框和下拉列表始终显示最新数据?

4

1 回答 1

0

我有一个解决方案。它不是很干净,但它很简单而且很有效。这是更新后的视图模型代码,有一些小改动和一个新方法:

using npAssemblies;
using System;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System.ComponentModel;

namespace combobox_test__01
{
    public class VM : INotifyPropertyChanged
    {
        private ObservableCollection<Person> people;
        public ObservableCollection<Person> People
        {
            get { return people; }
            set
            {
                 people = value;
                OnPropertyChanged("People");
            }
        }

        private Person selectedPerson;
        public Person SelectedPerson
        {
            get { return selectedPerson; }
            set
            {
                selectedPerson = value;
                OnPropertyChanged("SelectedPerson");
            }
        }

        private int cboPeopleSelectedIndex;
        public int CboPeopleSelectedIndex
        {
            get { return cboPeopleSelectedIndex; }
            set
            {
                cboPeopleSelectedIndex = value;
                OnPropertyChanged("CboPeopleSelectedIndex");
            }
        }

        private int newPidx = 0;
        private string[,] newP;
        private string DefaultPic = @"D:\Visual Studio Testing\Icons\user-icon.png",
            NewPic = @"D:\Visual Studio Testing\Small size pictures\mugshot";
        private Random random = new Random();

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

        public VM()
        {
            People = new ObservableCollection<Person>()
            {
                new Person() {Name="Aa", Type="Taa", PicFullPath=DefaultPic},
                new Person() {Name="Bb", Type="Tbb", PicFullPath=DefaultPic},
                new Person() {Name="Cc", Type="Tcc", PicFullPath=DefaultPic}
            };

            newP = new string[4, 3] { { "Dd", "Tdd", "" }, { "Ee", "Tee", "" }, { "Ff", "Tff", "" }, { "Gg", "Tgg", "" } };
        }

        public ICommand AlterRowCommand => new RelayCommandBase(AlterRow);
        private void AlterRow(object parameter)
        {
            //Make SelectedPerson.Type into a string ending with 2 randomish digits
            string fmts, picChoice;
            GetDifferentData(out fmts, out picChoice);
            string s = SelectedPerson.Type.Substring(0,3).PadRight(5);
            SelectedPerson.Type = s + fmts;
            // Use those 2 randomish digits to choose a picture from existing ones
            SelectedPerson.PicFullPath = NewPic + picChoice;
            // refresh the control the collection is bound to
            ResetBoundItems(SelectedPerson);
        }

        public ICommand NewRowCommand => new RelayCommandBase(NewRow);
        private void NewRow(object parameter)
        {
            string fmts, picChoice;
            int highIdx = People.Count;
            GetDifferentData(out fmts, out picChoice);
            Person newPerson = new Person() { Name = newP[newPidx, 0], Type = newP[newPidx++, 1], PicFullPath = NewPic + picChoice };
            People.Add(newPerson);
            // refresh the control the collection is bound to and select the new row
            ResetBoundItems(newPerson, highIdx);
        }

        private void GetDifferentData(out string fmts, out string picChoice)
        {
            int randomi = random.Next(1, 35);
            fmts = randomi.ToString("D2");
            picChoice = fmts + ".jpg";
        }

        private void ResetBoundItems(Person inPerson, int gotoIndex = -1)
        {
            // refreshes the display of the combobox otherwise it is visually stale
            int idx = gotoIndex;
            if (gotoIndex == -1)
            {
                // a preferred index was not supplied; use the currently selected index
                idx = CboPeopleSelectedIndex;
            }
            // save a copy of the current selected person
            Person tmpP = (Person)inPerson.Clone();
            // save the current ItemsSource
            ObservableCollection<Person> refPC = new ObservableCollection<Person>();
            refPC = People;
            // reset the ItemsSource
            ObservableCollection<Person> tmpPC = new ObservableCollection<Person>();
            People = tmpPC;
            // set it back
            People = refPC;
            // restore the selected person
            SelectedPerson = (Person)tmpP.Clone();
            // select the relevant row
            CboPeopleSelectedIndex = idx;
        }
    }

    public class Person
    {
        public string Name { get; set; }
        public string Type { get; set; }
        public string PicFullPath { get; set; }

        public Person Clone()
        {
            return (Person)this.MemberwiseClone();
        }
    }
}

视图需要 ComboBoxes 有一个绑定 SelectedIndex 所以添加

SelectedIndex="{Binding CboPeopleSelectedIndex}" 

到 ComboBoxes 定义。

新方法ResetBoundItems()处理刷新。Person 类需要一个Clone()方法。我让 ComboBox 在添加时选择了新行。此解决方案也适用于 ListBoxes。

于 2017-11-23T12:14:55.350 回答