WPF уведомил о том, что поле выбора ComboBox и элемент раскрывающегося списка не синхронизируются и не обновляются быстро
Я хочу, чтобы поля со списком в моей форме 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.
Как сделать так, чтобы в полях выбора и раскрывающихся списках всегда отображались последние данные?
1 ответ
У меня есть решение. Это не очень чисто, но это просто, и это работает. Это обновленный код модели представления с несколькими небольшими изменениями и новым методом:
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();
}
}
}
Представлению нужны комбинированные списки для привязки SelectedIndex, поэтому добавьте
SelectedIndex="{Binding CboPeopleSelectedIndex}"
к определению ComboBoxes.
Новый метод ResetBoundItems()
обрабатывает освежающий Класс Person нуждается в Clone()
метод. Я сделал ComboBox выбрать новую строку при добавлении. Это решение будет работать и для ListBoxes.