Есть приложение в котором пользователь может указывать название продукта и его цену, по нажатию на кнопку эти данные вносятся в ObservableCollection и сразу же отображаются в DataGrid. Также в окне присутствует textbox в котором должна отображаться средняя цена всех внесенных продуктов. Проблема в следующем: Нужно чтобы пользователь в DataGrid мог изменять цену уже внесённых продуктов после чего сразу же должна изменяться средняя цена (AvaragePrice) всех продуктов, а этого не происходит, данные в коллекции изменяются, но изменения вышеуказанного свойства не происходит: MainWindow.xaml:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid Background="Transparent" ItemsSource="{Binding Path=AllProducts, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged,
NotifyOnSourceUpdated=True}"
AutoGenerateColumns="False" HorizontalAlignment="Center"
FontSize="16" Foreground="Black" CanUserReorderColumns="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridTextColumn Header="Price" Binding="{Binding Path=Price, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataGrid.Columns>
</DataGrid>
<Grid Grid.Column="1">
<!--Разбиваем наш грид на строки-->
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Content="Name of product:" FontSize="16"
Foreground="White" VerticalAlignment="Center"/>
<Label Grid.Row="1" Content="Price (zl):"
FontSize="16" Foreground="White"
VerticalAlignment="Center"/>
<TextBox Grid.Column="1" Background="Transparent"
Height="35" Foreground="White"
FontSize="18" Width="200" CaretBrush="White"
HorizontalAlignment="Left" Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="1" Grid.Column="1"
Background="Transparent" Height="35"
Foreground="White" FontSize="18"
Width="70" HorizontalAlignment="Left"
Text="{Binding Path=Price, UpdateSourceTrigger=PropertyChanged, StringFormat=N2}"/>
</Grid>
<Button Grid.Row="1" Background="Transparent"
Foreground="White" FontSize="16"
Width="100" Height="40"
Content="Dodaj" BorderThickness="2"
BorderBrush="WhiteSmoke" Command="{Binding Path=AddProduct}"/>
<Grid Grid.Row="2" Width="250">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Background="Transparent" Foreground="White"
FontSize="16" Content="Avarage price:"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock Grid.Column="1" Background="Transparent"
Foreground="White" FontSize="18" VerticalAlignment="Center"
Text="{Binding Path=AvaragePrice, UpdateSourceTrigger=PropertyChanged, StringFormat=N2}"/>
</Grid>
</Grid>
</Grid>
MainViewModel:
class MainViewModel : BaseModel
{
MainModel mainModel;
public MainViewModel()
{
// Инициализируем модель, подписываемся на изменение любого из её свойств
mainModel = new MainModel();
mainModel.PropertyChanged += (s, e) => { OnPropertyChanged(e.PropertyName); };
}
public string Name
{
get
{
return mainModel.Name;
}
set
{
mainModel.Name = value;
OnPropertyChanged();
}
}
public double Price
{
get
{
return mainModel.Price;
}
set
{
mainModel.Price = value;
OnPropertyChanged();
}
}
public RelayCommand AddProduct
{
get
{
return mainModel.AddProduct;
}
}
public ObservableCollection<Product> AllProducts
{
get
{
return mainModel.AllProducts;
}
set
{
mainModel.AllProducts = value;
OnPropertyChanged();
}
}
public double AvaragePrice
{
get
{
return mainModel.AvaragePrice;
}
set
{
mainModel.AvaragePrice = value;
OnPropertyChanged();
}
}
}
MainModel:
class MainModel : BaseModel
{
public MainModel()
{
allProducts = new ObservableCollection<Product>();
allProducts.CollectionChanged += (s,e) => { AvaragePrice = allProducts.Sum(x => x.Price) / allProducts.Count; };
}
string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged();
}
}
double price;
public double Price
{
get
{
return price;
}
set
{
price = value;
OnPropertyChanged();
}
}
ObservableCollection<Product> allProducts;
public ObservableCollection<Product> AllProducts
{
get
{
return allProducts;
}
set
{
allProducts = value;
OnPropertyChanged();
AvaragePrice = allProducts.Sum(x => x.Price) / allProducts.Count;
}
}
RelayCommand addProduct;
public RelayCommand AddProduct
{
get
{
return addProduct ?? (addProduct = new RelayCommand(obj =>
{
Product newProduct = new Product()
{
Price = this.Price,
Name = this.Name
};
AllProducts.Add(newProduct);
}));
}
}
double avaragePrice;
public double AvaragePrice
{
get
{
return avaragePrice;
}
set
{
avaragePrice = value;
OnPropertyChanged();
}
}
}
Смотрите в общем как можно поступить (за основу взял ответ с En SO)...
Для примера нам (мне) понадобится набросать небольшой View:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" FontSize="16" Text="{Binding AvaragePrice, StringFormat={}В среднем: {0}$}" HorizontalAlignment="Center"/>
<ListBox Grid.Row="1" ItemsSource="{Binding Items}" HorizontalContentAlignment="Stretch" BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid d:DataContext="{d:DesignInstance {x:Type local:ItemModel}}" >
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}"/>
<TextBox Grid.Column="1" Text="{Binding Price, StringFormat={}{0}$}" BorderThickness="0"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Также нам понадобится INotifyPropertyChanged, для удобства создадим отдельный класс:
public class VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Итак, скажем, у нас есть VM для наших предметов и некая модель самого предмета:
C моделью предмета думаю все понятно, просто объявляем необходимые свойства (в моем случае это имя и цена).
public class ItemModel : VM
{
public string Name { get; set; }
private int price;
public int Price
{
get => price;
set
{
price = value;
OnPropertyChanged();
}
}
}
В VM у нас пока будет все тоже, но реализуем коллекцию предметов и среднюю цену, а также, давайте сделаем метод, который будет обновлять цену:
public class ItemsViewModel : VM
{
public ObservableCollection<ItemModel> Items { get; set; } = new ObservableCollection<ItemModel>();
private int avaragePrice;
public int AvaragePrice
{
get => avaragePrice;
set
{
avaragePrice = value;
OnPropertyChanged();
}
}
public void UpdatePrice()
{
AvaragePrice = Items.Sum(x => x.Price) / Items.Count;
}
}
И так, теперь у вас есть выбор 1. Подписывать все ItemModel на событие изменение. 2. Использовать BindingList.
Для начала подпишемся на событие изменения коллекции в нашей VM:
public ItemsViewModel()
{
Items.CollectionChanged += ItemsOnCollectionChanged;
}
private void ItemsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
foreach (INotifyPropertyChanged item in e.OldItems)
item.PropertyChanged -= UpdatePrice;
}
if (e.NewItems != null)
{
foreach (INotifyPropertyChanged item in e.NewItems)
item.PropertyChanged += UpdatePrice;
}
}
Изменим немного метод обновления:
public void UpdatePrice(object sender, PropertyChangedEventArgs e)
{
AvaragePrice = Items.Sum(x => x.Price) / Items.Count;
}
Вроде все... И так, что здесь происходит? Суть в следующем: ObservableCollection оповещает только если в коллекцию добавляется, либо что то удаляется. В этом случае мы при добавление предмета в коллекцию проходимся по всем его значениям и подписываемся на событие изменения, если наш предмет в коллекции реализует INotifyPropertyChanged. При удаление делаем обратное, то есть отписываемся. Таким образом, все Model внутри коллекции будут подписаны на событие обновление цены.
Есть довольно классная штука в WPF, как BindingList. У нее есть событие ListChanged, которое в свою очередь оповещает о любом (вроде) изменении в коллекции.
ObservalCollection<ItemModel>
на BindingList<ItemModel>
.В конструкторе подпишемся на событие изменения, ну и обновление цены можно положить внутрь, я думаю...:
public ItemsViewModel()
{
Items.ListChanged += ItemsOnListChanged;
}
private void ItemsOnListChanged(object sender, ListChangedEventArgs e)
{
if (e.ListChangedType == ListChangedType.ItemChanged)
{
AvaragePrice = Items.Sum(x => x.Price) / Items.Count;
}
}
public BindingList<ItemModel> Items { get; set; } = new BindingList<ItemModel>();
Ну тут, я думаю, все понятно и объяснять не нужно. Если у нас значение изменено, то обновляем цену (тут можете тип выбрать нужный).
В общем, результат у нас будет примерно следующий:
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Я написал большой скрипт используя SystemNumerics, ошибок в коде не было, Visual Studio его нормально воспринимал
Как известно, этот контрол ведет себя совсем не так, как обычный TextBox, особенно напрягают возможность вставки изображений посреди текста,...
Как этот код описать в регулярном выражении?