Calculating total of a ListView column WPF C# - c#

I'm creating an EPOS system for a bar, for my own project just to test my skills.
I've come into a problem, I've managed to put all products in a WrapPanel and when clicked ive also managed to get them to show in a ListView control.
However, I cannot seem to get the total to show in a label below the ListView, essentially, each time a product is added to the ListView i want the total to also be updated by adding up all of the prices in the "Price" column and displaying them in a label below. But i cannot even seem to print the total via a button let alone do it automatically.
Here is my code for the button so far.
Don't suggest SubItems as it doesnt work in WPF.
private void Button_Click_1(object sender, RoutedEventArgs e) {
decimal total = 0;
foreach (ListViewItem o in orderDetailsListView.Items)
{
total = total + (decimal)(orderDetailsListView.SelectedItems[1]);
}
totalOutputLabel.Content = total;
}

I wrote this below answer to another question of yours on this same program that you deleted before I could post it. It covers updating the price but it also covers a lot more (using information that was in the deleted question).
First of all, if you want the screen to update when the items in the list are updated you must make the class implement INotifyPropertyChanged
public class OrderDetailsListItem : INotifyPropertyChanged
{
private string _name;
private decimal _price;
private int _quantity;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
public decimal Price
{
get { return _price; }
set
{
if (value == _price) return;
_price = value;
OnPropertyChanged();
}
}
public int Quantity
{
get { return _quantity; }
set
{
if (value == _quantity) return;
_quantity = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now when the Price or Quantity is changed it will let the bindings know that the item was changed.
Next the reason that your if (OrderItem.Contains( caused duplicate items to show up is you must implement Equals( (and preferably GetHashCode()) for things like Contains( to work.
public class OrderDetailsListItem : INotifyPropertyChanged, IEquatable<OrderDetailsListItem>
{
//(Snip everything from the first example)
public bool Equals(OrderDetailsListItem other)
{
if (ReferenceEquals(null, other)) return false;
return string.Equals(_name, other._name);
}
public override bool Equals(object obj)
{
return Equals(obj as OrderDetailsListItem);
}
public override int GetHashCode()
{
return (_name != null ? _name.GetHashCode() : 0);
}
}
Another point, don't do OrderItem.CollectionChanged += in your button click, you will be creating extra event calls every collection changed event. Just set it one in the constructor and that is the only even subscription you need. However, there is a even better collection to use, BindingList<T> and its ListChanged event. A BindingList will raise the ListChange event in all the situations that ObserveableCollection raises CollectionChanged but in addition it will also raise the event when any item in the collection raises the INotifyPropertyChanged event.
public MainWindow()
{
_orderItem = new BindingList<OrderDetailsListItem>();
_orderItem.ListChanged += OrderItemListChanged;
InitializeComponent();
GetBeerInfo();
//You will see why all the the rest of the items were removed in the next part.
}
private void OrderItemListChanged(object sender, ListChangedEventArgs e)
{
TotalPrice = OrderItem.Select(x => x.Price).Sum();
}
Lastly, I would bet you came from a Winforms background. WPF is based around binding a lot more than winforms, I used to write code a lot like what you are doing before I really took that point in. All of those assignments to labels and collections should be done in the XAML with bindings, this allows for things like the INotifyPropertyChanged events to automatically update the screen without needing a function call.
Here is a simple recreation of your program that runs and uses bindings and all of the other things I talked about.
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myNamespace ="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<Button Content="{x:Static myNamespace:GlobalVariables._amstelProductName}" Click="amstelBeerButton_Click"/>
<TextBlock Text="{Binding TotalPrice, StringFormat=Total: {0:c}}"/>
</StackPanel>
<ListView Grid.Column="1" ItemsSource="{Binding OrderItem}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=Name}" Header="Name"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Price, StringFormat=c}" Header="Price"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Quantity, StringFormat=N0}" Header="Quantity"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
using System;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public static readonly DependencyProperty TotalPriceProperty = DependencyProperty.Register(
"TotalPrice", typeof (decimal), typeof (MainWindow), new PropertyMetadata(default(decimal)));
private readonly BindingList<OrderDetailsListItem> _orderItem;
public MainWindow()
{
_orderItem = new BindingList<OrderDetailsListItem>();
_orderItem.ListChanged += OrderItemListChanged;
InitializeComponent();
DataContext = this;
GetBeerInfo();
}
public BindingList<OrderDetailsListItem> OrderItem
{
get { return _orderItem; }
}
public decimal TotalPrice
{
get { return (decimal) GetValue(TotalPriceProperty); }
set { SetValue(TotalPriceProperty, value); }
}
private void GetBeerInfo()
{
OrderItem.Add(new OrderDetailsListItem
{
Name = "Some other beer",
Price = 2m,
Quantity = 1
});
}
private void OrderItemListChanged(object sender, ListChangedEventArgs e)
{
TotalPrice = _orderItem.Select(x => x.Price).Sum();
}
private void amstelBeerButton_Click(object sender, RoutedEventArgs e)
{
//This variable makes me suspicous, this probibly should be a property in the class.
var quantityItem = GlobalVariables.quantityChosen;
if (quantityItem == 0)
{
quantityItem = 1;
}
var item = OrderItem.FirstOrDefault(i => i.Name == GlobalVariables._amstelProductName);
if (item == null)
{
OrderItem.Add(new OrderDetailsListItem
{
Name = GlobalVariables._amstelProductName,
Quantity = quantityItem,
Price = GlobalVariables._amstelPrice
});
}
else if (item != null)
{
item.Quantity = item.Quantity + quantityItem;
item.Price = item.Price*item.Quantity;
}
//The UpdatePrice function is nolonger needed now that it is a bound property.
}
}
public class GlobalVariables
{
public static int quantityChosen = 0;
public static string _amstelProductName = "Amstel Beer";
public static decimal _amstelPrice = 5;
}
public class OrderDetailsListItem : INotifyPropertyChanged, IEquatable<OrderDetailsListItem>
{
private string _name;
private decimal _price;
private int _quantity;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
public decimal Price
{
get { return _price; }
set
{
if (value == _price) return;
_price = value;
OnPropertyChanged();
}
}
public int Quantity
{
get { return _quantity; }
set
{
if (value == _quantity) return;
_quantity = value;
OnPropertyChanged();
}
}
public bool Equals(OrderDetailsListItem other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(_name, other._name);
}
public event PropertyChangedEventHandler PropertyChanged;
public override bool Equals(object obj)
{
return Equals(obj as OrderDetailsListItem);
}
public override int GetHashCode()
{
return (_name != null ? _name.GetHashCode() : 0);
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Just did a test on this, you should make sure you are getting into the event handler by adding a breakpoint. If you are not, make sure you have registered the handler to the click event, e.g.:
<Button Name="TestButton" Click="Button_Click_1"/>
If you are playing around with WPF I would strongly suggest looking at MVVM and data binding at some point.

Related

WPF DataGrid, ObservableCollection Updates, and Redrawing

I will try to keep this as brief as possible, but there is a fair amount of nuance to this question.
The Workflow
I am working in C# and am using WPF and MVVM for the UI for an addin for Revit (a 3D modeling software from Autodesk).
The overarching goal is to create a window that shows the parameters of a 3D element after it is selected. This is a filtered list that are specific to my organization and our users needs, and allow the user to edit them in order to streamline their workflow. The complication is that because I am working with an API I can only use what tools I am given when interacting with the model.
The issue I am running into lies in the workflow. I have detailed the workflow below.
User Selects a 3D Element
The addin uses the API to pull the parameters and wraps them in a wrapper class and adds them into a custom ObservableCollection to display them in the DataGrid
The user then changes a value in the DataGrid. When the cell loses focus it fires off a command that hooks the API and updates the parameter's value.
The change is made and the internal logic of the element calculates it's values based on the changed parameter
The calculated parameter values are changed in the model
The ViewModel checks each parameter to see if its value has changed, and updates any of the wrapped parameters in the ObservableCollection to reflect the changes.
The ObservableCollection fires off it's collection changed event to notify the DataGrid that values have changed
The DataGrid updates it's values to complete the process.
The issue currently lies in the very last step. Once the collection changed event is complete the wrapped parameter value matches the parameter value from the API, but the DataGrid will not redraw the information. Once you minimize the window, click into the cell, or scroll the DataGrid to where the cell is not visible the cell will show the new value when it comes back into view.
I can't seem to find a way to keep with MVVM principles and force the cells to redraw with their updated value. Am I missing something with this? How do I get the DataGrid to update without having to completely clear and reset the ObservableCollection items?
Things I have Tried
I had to create a custom ObservableCollection to implement INPC for the items in the collection, and from debugging it appears to work as intended. Each time an item in the ObservableCollection is updated it makes the change subscribes it to INPC and raises the collection changed event.
For each of the columns I have the binding set to Mode="TwoWay" and have tried setting the UpdateSourceTrigger="PropertyChanged", and neither helped.
I originally was using a <ContentPresenter/> in a <DataGridTemplteColumn/> to present different cell types, but even using a basic <DataGridTextColumn doesn't work.
---- CODE ----
XAML:
<DataGrid Grid.Row="2" ItemsSource="{Binding TestingParameters, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn IsReadOnly="True" Binding="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Name"/>
<DataGridTextColumn IsReadOnly="False" Binding="{Binding Path=ParamValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Param Value">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding UpdateParametersCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
C# ViewModel
public class ViewModelParameterPane : ViewModelBase
{
private ExternalEvent _event;
private HandlerED _handler;
private UIApplication _uiapp;
private UIDocument _uidoc;
private Document _doc;
private TestObservableCollection<WrappedParameter> _testParameter = new TestObservableCollection<WrappedParameter>();
public TestObservableCollection<WrappedParameter> TestParameter
{
get => _testParameter;
set
{
_testParameter = value;
RaiseProperty(nameof(_testParameter));
}
}
public ViewModelParameterPane(ExternalEvent exEvent, HandlerED handler, UIApplication uiapp)
{
_event = exEvent;
_handler = handler;
_uiapp = uiapp;
_uidoc = _uiapp.ActiveUIDocument;
_doc = _uidoc.Document;
_testParameter.ItemPropertyChanged += _testParameter_ItemPropertyChanged;
_testParameter.CollectionChanged += _testParameter_CollectionChanged;
UpdateParametersCommand = new RelayCommand(CallUpdateParameters);
}
private void _testParameter_ItemPropertyChanged(object sender, ItemPropertyChangedEventArgs<WrappedParameter> e)
{
Debug.WriteLine("PROPERTY CHANGE");
RaiseProperty(nameof(TestParameter));
int index = TestParameter.IndexOf(e.Item);
TestParameter[index] = e.Item;
}
private void _testParameter_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Debug.WriteLine("COLLECTION CHANGE");
}
private void MakeRequest(RequestIdED request)
{
_handler.Request.Make(request);
_event.Raise();
}
private void CallUpdateParameters() { MakeRequest(RequestIdED.UpdateParameters); }
public void UpdateParameters()
{
Debug.WriteLine("Running Update Parameters");
try
{
using (var transaction = new Transaction(_doc))
{
transaction.Start("T_UpdateParameters");
foreach (WrappedParameter p in TestParameter)
{
string currenValue = p.RevitParameter.AsValueString();
if (p.RevitParameter.AsValueString() != p.ParamValue)
{
bool setValueSuccess = p.SetRevitParameterValue(p.ParamValue);
if(!setValueSuccess)
{
TaskDialog.Show("Parameter Value Not Set", "The parameter value for the parameter " + p.Name + " was not given a valid value and was not changed. Please ensure the units are correct.");
}
}
}
transaction.Commit();
}
}
catch (Exception e)
{
throw new Exception("Something Went Wrong. Check your values.");
}
}
public void UpdateParameterValues()
{
for(var i = 0; i < TestParameter.Count; i++)
{
TestParameter[i].UpdateValues();
}
}
}
C# Parameter Wrapper Class
public class TestParameter : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
internal void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(nameof(_name));
}
}
private string _categories;
public string Categories
{
get => _categories;
set
{
_categories = value;
OnPropertyChanged(nameof(_categories));
}
}
private string _paramValue;
public string ParamValue
{
get => _paramValue;
set
{
_paramValue = value;
OnPropertyChanged(nameof(_paramValue));
}
}
private Parameter _revitParameter;
public Parameter RevitParameter
{
get => _revitParameter;
set
{
_revitParameter = value;
OnPropertyChanged(nameof(_revitParameter));
}
}
private ElementId _elementId;
public ElementId ElementId
{
get => _elementId;
set
{
_elementId = value;
OnPropertyChanged(nameof(_elementId));
}
}
public TestParameter(Parameter param)
{
GetRevitParameterValue();
}
public void GetRevitParameterValue()
{
//Get parameter value logic
}
public bool SetRevitParameterValue(string Value)
{
//Set parameter value logic
}
}
C# TestObservableCollection Class
public class TestObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public event EventHandler<ItemPropertyChangedEventArgs<T>> ItemPropertyChanged;
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
item.PropertyChanged += item_PropertyChanged;
}
protected override void RemoveItem(int index)
{
var item = this[index];
base.RemoveItem(index);
item.PropertyChanged -= item_PropertyChanged;
}
protected override void ClearItems()
{
foreach (var item in this)
{
item.PropertyChanged -= item_PropertyChanged;
}
base.ClearItems();
}
protected override void SetItem(int index, T item)
{
var oldItem = this[index];
oldItem.PropertyChanged -= item_PropertyChanged;
base.SetItem(index, item);
item.PropertyChanged += item_PropertyChanged;
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnItemPropertyChanged((T)sender, e.PropertyName);
}
private void OnItemPropertyChanged(T item, string propertyName)
{
var handler = this.ItemPropertyChanged;
if (handler != null)
{
handler(this, new ItemPropertyChangedEventArgs<T>(item, propertyName));
}
}
}
public sealed class ItemPropertyChangedEventArgs<T> : EventArgs
{
private readonly T _item;
private readonly string _propertyName;
public ItemPropertyChangedEventArgs(T item, string propertyName)
{
_item = item;
_propertyName = propertyName;
}
public T Item
{
get { return _item; }
}
public string PropertyName
{
get { return _propertyName; }
}
}

Async method call on ComboBox selection with MVVM

I'm facing a problem with displaying graphs filtered by ComboBox selection without having the UI lock up. The statistic filtering is quite heavy and needs to run async. This works fine all up until I try to call FilterStatisticsAsync and MonthSelectionChanged from the Property setter. Does anyone have a good tip on how to solve or work around this?
The XAML looks like this:
<ComboBox x:Name="cmbMonth"
ItemsSource="{Binding Months}"
SelectedItem="{Binding SelectedMonth }"
IsEditable="True"
IsReadOnly="True"
And the ViewModel property setter like this:
public string SelectedMonth
{
get { return _selectedMonth; }
set { SetProperty(ref _selectedMonth, value); LoadStatisticsAsync(); MonthSelectionChanged(); }
}
SetProperty derives from a base class which encapsulates INPC like this:
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void SetProperty<T>(ref T member, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(member, value))
return;
member = value;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
I would do it using this:
public class AsyncProperty<T> : INotifyPropertyChanged
{
public async Task UpdateAsync(Task<T> updateAction)
{
LastException = null;
IsUpdating = true;
try
{
Value = await updateAction.ConfigureAwait(false);
}
catch (Exception e)
{
LastException = e;
Value = default(T);
}
IsUpdating = false;
}
private T _value;
public T Value
{
get { return _value; }
set
{
if (Equals(value, _value)) return;
_value = value;
OnPropertyChanged();
}
}
private bool _isUpdating;
public bool IsUpdating
{
get { return _isUpdating; }
set
{
if (value == _isUpdating) return;
_isUpdating = value;
OnPropertyChanged();
}
}
private Exception _lastException;
public Exception LastException
{
get { return _lastException; }
set
{
if (Equals(value, _lastException)) return;
_lastException = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Definition of property
public AsyncProperty<string> SelectedMonth { get; } = new AsyncProperty<string>();
somewhere else in your code:
SelectedMonth.UpdateAsync(Task.Run(() => whateveryourbackground work is));
binding in xaml:
SelectedItem="{Binding SelectedMonth.Value }"
Note that properties should reflect a current state, instead of triggering processes which may take an indefinite amount of time. Hence the need to update the property in a different way from just assigning it.

Why can't I reflect a list box choice in a text box in WPF?

I'm new to WPF and I'm having some trouble with my existing setup to get the list box selected item to appear in the text box.
The picture here represents the issue. I typed "12 HOUR" in the text box, which then filters the listbox to those items with "12 HOUR" anywhere in the string. But when I click "12 Hour Nasal" in the list box, I now want to reflect that choice back in the text box:
http://i.imgur.com/ZCYAolT.png
Here is my XAML for the user control containing the listbox and textbox:
<UserControl x:Class="SCM_AllergyRecModule.SearchAndSelectView"
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"
mc:Ignorable="d">
<StackPanel Width="300">
<TextBox x:Name="Filter" Text="{Binding Path=Filter, UpdateSourceTrigger=PropertyChanged}"/>
<ListBox Width ="300" Height="50" x:Name="ListBoxControl"
ItemsSource="{Binding Path=Allergens}"
SelectedItem="{Binding Path=SelectedAllergen}">
</ListBox>
</StackPanel>
And here is the ViewModel:
namespace SCM_AllergyRecModule
{
public class SearchAndSelectViewModel
{
private ICollectionView allergens;
private string selectedAllergen;
private string filter = "";
public string Filter
{
get
{
return this.filter.ToUpperInvariant();
}
set
{
if (this.filter != value)
{
this.filter = value;
this.Allergens.Refresh();
}
}
}
private bool ContainsFilter(object item)
{
var product = item as string;
if (product == null)
{
return false;
}
if (string.IsNullOrEmpty(this.Filter))
{
return true;
}
if (product.ToUpperInvariant().Contains(this.Filter))
{
return true;
}
return false;
}
public SearchAndSelectViewModel()
{
var cvs = new CollectionViewSource();
cvs.Source = MainWindow.scmAllergens;
this.allergens = cvs.View;
this.allergens.Filter = ContainsFilter;
}
public ICollectionView Allergens
{
get
{
return this.allergens;
}
}
public string SelectedAllergen
{
get
{
return this.selectedAllergen;
}
set
{
if (this.selectedAllergen != value)
{
this.selectedAllergen = value;
}
}
}
}
}
Update 1
I added the INotifyPropertyChanged interface to my class and have it being raised on SelectedAllergen in the setter. I added an event handler called SearchAndSelectViewModel_PropertyChanged to handle the SelectedAllergen property changing and set it in the constructor.
Now when I click an item in the listbox, I do see it setting the Filter to the SelectedItem and the list filters to that item so nothing else shows. But still, the text box text is not changing? See screenshot below. This is after I typed in "PEAN" in the textbox, then the listbox filtered to two choices, and I chose "PEANUTS (FOOD)", which then refiltered the list box to just show that choice but didn't set the text box to "PEANUTS (FOOD)":
http://imgur.com/dNxuVI5
Updated ViewModel
public class SearchAndSelectViewModel : INotifyPropertyChanged
{
private ICollectionView allergens;
private string selectedAllergen;
private string filter;
public string Filter
{
get
{
return this.filter.ToUpperInvariant();
}
set
{
if (this.filter != value)
{
this.filter = value;
this.Allergens.Refresh();
}
}
}
private bool ContainsFilter(object item)
{
var product = item as string;
if (product == null)
{
return false;
}
if (string.IsNullOrEmpty(this.Filter))
{
return true;
}
if (product.ToUpperInvariant().Contains(this.Filter))
{
return true;
}
return false;
}
private void SearchAndSelectViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "SelectedAllergen":
this.Filter = this.SelectedAllergen;
break;
}
}
public SearchAndSelectViewModel()
{
filter = "";
var cvs = new CollectionViewSource();
cvs.Source = MainWindow.scmAllergens;
this.allergens = cvs.View;
this.allergens.Filter = ContainsFilter;
this.PropertyChanged += SearchAndSelectViewModel_PropertyChanged;
}
public ICollectionView Allergens
{
get
{
return this.allergens;
}
}
public string SelectedAllergen
{
get
{
return this.selectedAllergen;
}
set
{
if (this.selectedAllergen != value && value != null)
{
this.selectedAllergen = value;
OnPropertyChanged("SelectedAllergen");
}
}
}
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You need to implement the INotifyPropertyChanged interface and you can raise it in your property setter. Since you are also binding your TextBox to the Filter property you need to set the Filter property when your SelectedAllergen changes.
INotifyPropertyChanged example:
public class MyViewModel : INotifyPropertyChanged
{
//...
private int myProperty = 0;
public int MyProperty
{
get { return myProperty; }
set
{
myProperty = value;
// Raise the property changed notification
OnPropertyChanged("MyProperty");
}
}
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if(handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Unable to bind inherited class to an datagrid wpf c#

//In Test.xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<DataGrid Grid.Row="0" AutoGenerateColumns="False" ItemsSource="{Binding}" Name="dtGridTran" HorizontalAlignment="Left" CanUserAddRows="True" >
<DataGrid.Columns>
<DataGridTextColumn Header="X" Binding="{Binding Path=X, UpdateSourceTrigger=PropertyChanged}" Width="200"/>
<DataGridTextColumn Header="Y" Binding="{Binding Path=Y, UpdateSourceTrigger=PropertyChanged}" Width="200" />
</DataGrid.Columns>
</DataGrid>
<Label Grid.Row="1" Content=" Total Sum of x and Y">
</Label>
<TextBox Grid.Row="1" Text="{Binding Path=Total, UpdateSourceTrigger=PropertyChanged}" Margin="150,0,0,0" Width="100"></TextBox>
</Grid>
//In Test.xaml.cs file
public partial class Test : Page
{
Derivedclass D = new Derivedclass();
public Test()
{
InitializeComponent();
this.DataContext = D;
dtGridTran.ItemsSource = new TestGridItems();
}
}
public class TestGridItems : List<Derivedclass>
{
}
// In Base class
public class Baseclass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int mTotal = 0;
private int mID = 0;
public int Total
{
get { return mTotal; }
set
{
mTotal = value; OnPropertyChanged("Total");
}
}
public int ID
{
get { return mID; }
set { mID = value; }
}
public string Item = "xxx";
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string PropertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
In Derivedclass.cs
public class Derivedclass : Baseclass, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int mX = 0;
private int mY = 0;
public int X
{
get { return mX; }
set
{
mX = value;
base.Total = base.Total + mX;
OnPropertyChanged("Total");
}
}
public int Y
{
get { return mY; }
set
{
mY = value;
base.Total = base.Total + mY;
OnPropertyChanged("Total");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string PropertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
}
Here am trying to find total sum of x and y value. but m getting total sum zero . total property is in base class. i want total sum of x and y column that i want to display in a textbox.. but Total property of baseclass return zero while adding multiple value.
The Total you're displaying belongs to a single D object which you create first but there is no connection to your List of DerivedClasses (which, I must say, you're wrapping weirdly as a separate class). Just because there is a base class it doesn't mean its properties will store values 'globally' so any instance can read them. A static property would act as one but in the scenario you're describing it would make more sense to designate a new property/variable outside your classes which would represent a sum. Besides, adding up numbers in a setter isn't going to work well as it will register any value changes and you might end up adding same property multiple times (unless this is what you're trying to achieve...).
Edit:
For starters, I would create a ViewModel class which your page's DataContext would bind to:
public class MyViewModel : INotifyPropertyChanged
{
// backing fields, etc...
public List<DerivedClass> Items
{
get { return items; }
set
{
items = value;
OnPropertyChanged("Items");
}
}
public int Sum
{
get
{
return this.Items != null ? this.Items.Sum(i => i.X + i.Y) : 0;
}
}
...
public void PopulateItems()
{
this.Items = MyMethodToGetItems();
foreach (var item in this.Items)
{
item.PropertyChanged += this.ItemPropertyChanged;
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
if (propertyChangedEventArgs.PropertyName == "X" || propertyChangedEventArgs.PropertyName == "Y")
{
OnPropertyChanged("Sum");
}
}
}
In the PopulateItems method it will subscribe to the PropertyChaned event of each item in the collection. If the property which triggered the even is either X or Y it will then fire another event to recalculate the sum (ItemPropertyChanged).
I believe the problem you are having is because of the separate implementations of INotifyPropertyChanged, removed the implementation from the DerivedClass and it should be ok, the binding is automatically hooking to the classes PropertyChanged event but because this is a different event to the base class it is not catching any base class PropertyChanged events being fired.
So the DerivedClass should just look like this
public class Derivedclass : Baseclass
{
private int mX = 0;
private int mY = 0;
public int X
{
get { return mX; }
set
{
mX = value;
base.Total = base.Total + mX;
OnPropertyChanged("Total");
}
}
public int Y
{
get { return mY; }
set
{
mY = value;
base.Total = base.Total + mY;
OnPropertyChanged("Total");
}
}
}
If you look at the warnings visual studio is giving you it has probably told you about this "method overrides non-virtual base class method, make base class method virtual or use keyword new"
Also I think your total calculation is messed up, you are always adding on to the total rather than just doing X + Y.

INotifyPropertyChanged 'Double' binding

I'm trying to bind some XAML code to a property in my ViewModel.
<Grid Visibility="{Binding HasMovies, Converter={StaticResources VisibilityConverter}}">
...
</Grid>
My ViewModel is setup like this:
private bool _hasMovies;
public bool HasMovies
{
get { return _hasMovies; }
set { _hasMovies = value; RaisePropertyChanged("HasMovies"); }
}
In the constructor of the ViewModel, I set the HasMovies link:
MovieListViewModel()
{
HasMovies = CP.Connection.HasMovies;
}
in CP:
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
private ObservableCollection<Movie> _movies;
public ObservableCollection<Movie> MovieList
{
get { return _movies; }
set
{
_movies = value;
RaisePropertyChanged("MovieList");
RaisePropertyChanged("HasMovies");
_movies.CollectionChanged += MovieListChanged;
}
}
private void MovieListChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("HasMovies");
}
What am I doing wrong? How should I change this binding so that it reflects the current state of CP.Connection.HasMovies?
Either directly expose the object in the ViewModel and bind directly through that (so that the value is not just copied once which is what happens now) or subscribe to the PropertyChanged event and set HasMovies to the new value every time it changes in your source object.
e.g.
CP.Connection.PropertyChanged += (s,e) =>
{
if (e.PropertyName = "HasMovies") this.HasMovies = CP.Connection.HasMovies;
};
First of all, the setter for a collection type, such as your MovieList property, is not called when you change the content of the collection (ie. Add/Remove items).
This means all your setter code for the MovieList property is pointless.
Secondly, it's very silly code. A much better solution, is to use NotifyPropertyWeaver. Then your code would look like this, in the viewmodel:
[DependsOn("MovieList")]
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
public ObservableCollection<Movie> MovieList
{
get;
private set;
}
Alternatively you would have to add a listener for the CollectionChanged event when you initialize the MovieList property the first time (no reason to have a backing property, really really no reason!), and then call RaisePropertyChanged("HasMovies") in the event handler.
Example:
public class CP : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public CP()
{
MovieList = new ObservableCollection<Movie>();
MovieList.CollectionChanged += MovieListChanged;
}
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
public ObservableCollection<Movie> MovieList
{
get;
private set;
}
private void MovieListChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("HasMovies");
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Categories