WPF ListView doesn't update on PropertyChanged - c#

I have a WPF list view that displays a material, it's thickness , and a unit for the thickess in a combo box...xaml looks like this (I eliminated all the visualization settings for clarity):
<ListView ItemsSource="{Binding Path=MaterialLayers}" IsSynchronizedWithCurrentItem="True">
<ListView.Resources>
<x:Array x:Key="DistanceUnitItems" Type="sys:String" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>cm</sys:String>
<sys:String>inches</sys:String>
</x:Array>
<DataTemplate x:Key="ThicknessUnit">
<ComboBox ItemsSource="{StaticResource DistanceUnitItems}" SelectedIndex="{Binding ThicknessUnit}" />
</DataTemplate>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="Material Name" DisplayMemberBinding="{Binding MaterialName}"/>
<GridViewColumn Header="Material Thickness" DisplayMemberBinding="{Binding MaterialThickness}"/> />
<GridViewColumn Header="Thickness Unit" CellTemplate="{StaticResource ThicknessUnit}" />
</GridView>
</ListView.View>
</ListView>
MaterialLayers is an ObservableCollection<MaterialLayer>
MaterialLayer has properties for MaterialName, MaterialThickness, and ThicknessUnit (which is 0 for cm and 1 for inches). MaterialThickness converts the internally stored value (which is in cm) to the unit specified by ThicknessUnit.
When ThicknessUnit is changed, my DataViewModel calls the PropertyChanged event handler with "MaterialLayers" as the property name.
So, I expected MaterialThickness to update automatically when ThicknessUnit was changed.
I've debugged it, and the PropertyChanged("MaterialLayers") gets called.(When the ThicknessUnit set method is called, it calls an event on the parent data class (MyData), which calls an event on DataViewModel which calls the PropertyChanged handler.)
Relevant code from DataViewModel
public delegate void DataChangedHandler(String identifier);
public DataViewModel()
{
Data = new MyData();
Data.DataChanged += new DataChangedHandler(RaisePropertyChanged);
}
public ObservableCollection<XTRRAMaterialLayer> MaterialLayers
{
get { return _data.MaterialLayers; }
set { }
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Relevant Code from MyData
public class XTRRAData
{
public ObservableCollection<XTRRAMaterialLayer> MaterialLayers { get; set; }
public event DataChangedHandler DataChanged;
public MyData()
{
MaterialLayers = new ObservableCollection<MaterialLayer>();
}
public void myDataChanged(String identifier)
{
DataChanged(identifier);
}
public void AddLayer()
{
MaterialLayer layer = new MaterialLayer() { MaterialName="Test", MaterialThickness=5, ThicknessUnit=0 };
layer.DataChanged += new DataChangedHandler(myDataChanged);
}
}
Relevant Code from MaterialLayer
public class XTRRAMaterialLayer
{
public XTRRAMaterialLayer()
{
_thicknessUnit = 0; // cm
}
public event DataChangedHandler DataChanged;
public String MaterialName { get; set; }
public double MaterialThickness
{
get
{
switch (_thicknessUnit)
{
default:
case 0:
return DIST;
case 1:
return DIST * 0.393700787;
}
}
set
{
switch (_thicknessUnit)
{
default:
case 0:
DIST = value;
break;
case 1:
DIST = value / 0.393700787;
break;
}
}
}
public int _thicknessUnit;
public int ThicknessUnit
{
get { return(int) _thicknessUnit; }
set
{
_thicknessUnit = (eThicknessUnits)value;
FireDataChanged("MaterialLayers");
}
}
public void FireDataChanged(String identifier)
{
if(DataChanged!=null)
{
DataChanged(identifier);
}
}
}
Can anyone help?

You need to implement INotifyPropertyChanged in the class where the property you are changing is - in your case XTRRAMaterialLayer, and raise the PropertyChanged event when the property changes.
Your properties should look something like this:
public int ThicknessUnit
{
get { return(int) _thicknessUnit; }
set
{
_thicknessUnit = (eThicknessUnits)value;
NotifyPropertyChanged();
}
}
The NotifyPropertyChanged event handler:
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

Related

How to update UI from List inside ObservableCollection?

I have ObservableCollection of DeviceInformation which is added in MainWindowViewModel and linked with DataContext.
public partial class MainWindow : Window
{
MainWindowViewModel viewModel = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = viewModel;
}
}
Here is the MainWindowViewModel:
public class MainWindowViewModel : ViewModelBase
{
private ObservableCollection<DeviceInformation> allDeviceInfo = new ObservableCollection<DeviceInformation>();
public MainWindowViewModel()
{
// here some of the commands
}
public ObservableCollection<DeviceInformation> AllDeviceInfo
{
get { return allDeviceInfo; }
set
{
allDeviceInfo = value;
this.RaisePropertyChanged(nameof(AllDeviceInfo));
}
}
}
The RaisePropertyChanged is done with implementing ViewModelBase which looks like this:
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
this.RaisePropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void RaisePropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
Inside my DeviceInformation I have a List of SyntaxMessages:
public class DeviceInformation : ViewModelBase
{
private List<SyntaxMessages> list = new List<SyntaxMessages>();
private string test = "";
public List<SyntaxMessages> ComConsoleMessages{
get { return list; } // get method
set
{
list = value;
RaisePropertyChanged(nameof(ComConsoleMessages));
} // set method
}
public string Test{
get { return test; } // get method
set
{
test = value;
RaisePropertyChanged(nameof(Test));
} // set method
}
}
This is how the SyntaxMessages looks:
public class SyntaxMessages : ViewModelBase
{
#region private values
private string message = "";
private string status = "";
private string color = "Transparent";
#endregion
#region Public values
public string Message {
get { return message; }
set
{
message = value;
RaisePropertyChanged(nameof(Message));
}
}
public string Status {
get { return status; }
set
{
status = value;
RaisePropertyChanged(nameof(Status));
}
}
public string Color {
get { return color; }
set
{
color = value;
RaisePropertyChanged(nameof(Color));
}
}
#endregion
}
So when I running my program and connecting device to it will collect and add information the the DeviceInformation and this will be added to ObervableCollection of DeviceInformation. This will update my MainTabControl by adding new tab and binding many strings like "Test" (there is more then one) to the TextBoxes, and also update the SubTabControl which is inside the main one. Inside both of the TabItems inside SubTabControl I also have a ListView to which I want link the List of SyntaxMessages this looks like this:
<ListView
Grid.Row="4"
Grid.Column="0"
Grid.ColumnSpan="5"
MinHeight="40"
Padding="0"
Margin="2"
ItemsSource="{Binding ComConsoleMessages, UpdateSourceTrigger=PropertyChanged}">
<ListView.View>
<GridView
AllowsColumnReorder="False">
<GridViewColumn
DisplayMemberBinding="{Binding Message}"
Header="Message" />
<GridViewColumn
Header="Status">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Status}" Foreground="{Binding Color}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Problem
All works fine except a ListView. When I add some SyntaxMessages to the List of SyntaxMessages called ComConsoleMessages then I have to switch between SubTabControl tabs(SubTabItem1/2) to see ListView updated. I want to update ListView every single time when new message is added to the List of SyntaxMessages which is inside DeviceInfromation which is indside ObservableCollection of DeviceInfromations that is linked via MainWindowViewMode to the window DataContext.
Here is the view:

Prevent Bound ComboBox from firing SelectionChanged Event at application startup

I have a ComboBox bound to a ViewModel Items Source List.
The ComboBox also has a SelectionChanged Event.
I want the event to fire only when the user has clicked the ComboBox and selected a new item.
But the event fires at application startup when the bound items are loaded and the default item is programmatically selected.
XAML
<ComboBox x:Name="cboDisplay"
ItemsSource="{Binding Display_Items}"
SelectedValue="{Binding Display_SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="10,10,0,0"
Height="22"
Width="100"
SelectionChanged="cboDisplay_SelectionChanged" />
C#
private void cboDisplay_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show("Event Fired");
}
ViewModel
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void OnPropertyChanged(string prop)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(prop));
}
}
public MainViewModel()
{
// Default Item at Startup
Display_SelectedItem = "Windowed";
}
// Items Source
//
private List<string> _Display_Items = new List<string>()
{
"Fullscreen",
"Windowed"
};
public List<string> Display_Items
{
get { return _Display_Items; }
set
{
_Display_Items = value;
OnPropertyChanged("Display_Items");
}
}
// Selected Item
//
private string _Display_SelectedItem { get; set; }
public string Display_SelectedItem
{
get { return _Display_SelectedItem; }
set
{
if (_Display_SelectedItem == value)
{
return;
}
_Display_SelectedItem = value;
OnPropertyChanged("Display_SelectedItem");
}
}

wpf - One ViewModel that interacts with multiple instances of a Model

I have a WorkspaceViewModel that handles addition and deletion of tab items dynamically through an ObservableCollection. Each time a tab is connected to a PayslipModel, all bindings work fine but one problem I am having is that;
I have a save button in the UserControl who's DataContext is set to WorkspaceViewModel and I would like to save whatever info is being displayed in the selected tab. Now, each time a tab is added, a new instance of PayslipModel is created, which is exactly what I want because I don't want bindings to be shared for all tabs. However, I am unable to save what is being displayed since PayslipModel has multiple instances, therefore nothing is returned (temporarily using MessageBox to test if info is being retrieved) when I hit save.
I created a diagram to better explain my situation:
Is it possible to access the current instance when a tab is selected or cycle through all instances and do something like batch saving?
This is a working example which shows one of the possiblities:
View
<TabControl DataContext="{Binding}" ItemsSource="{Binding Models}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" >
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<DockPanel>
<Button DockPanel.Dock="Top" Content="Click Me" Command="{Binding DataContext.PCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabControl}}"
CommandParameter="{Binding Desc}"/>
<TextBlock Text="{Binding Desc}" >
</TextBlock>
</DockPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Model View
public class ModelView
{
public ModelView()
{
_models = new ObservableCollection<Model>();
_pCommand = new Command(DoParameterisedCommand);
}
ObservableCollection<Model> _models;
public ObservableCollection<Model> Models { get { return _models; } }
private void DoParameterisedCommand(object parameter)
{
MessageBox.Show("Parameterised Command; Parameter is '" +
parameter.ToString() + "'.");
}
Command _pCommand;
public Command PCommand
{
get { return _pCommand; }
}
}
Model
public class Model : INotifyPropertyChanged
{
string _desc;
public string Desc { get { return _desc; } set { _desc = value; RaisePropertyChanged("Desc"); } }
string _name;
public string Name { get { return _name; } set { _name = value; RaisePropertyChanged("Name"); } }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
Command
public class Command : ICommand
{
public Command(Action<object> parameterizedAction, bool canExecute = true)
{
_parameterizedAction = parameterizedAction;
_canExecute = canExecute;
}
Action<object> _parameterizedAction = null;
bool _canExecute = false;
public bool CanExecute
{
get { return _canExecute; }
set
{
if (_canExecute != value)
{
_canExecute = value;
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}
public event EventHandler CanExecuteChanged;
bool ICommand.CanExecute(object parameter)
{
return _canExecute;
}
void ICommand.Execute(object parameter)
{
this.DoExecute(parameter);
}
public virtual void DoExecute(object param)
{ if (_parameterizedAction != null)
_parameterizedAction(param);
else
throw new Exception();
}
}
Use this to initialize:
public MainWindow()
{
InitializeComponent();
ModelView mv = new ModelView();
mv.Models.Add(new Model() { Name = "a", Desc = "aaa" });
mv.Models.Add(new Model() { Name = "b" , Desc = "bbb"});
mv.Models.Add(new Model() { Name = "c", Desc = "cccc" });
this.DataContext = mv;
}

PropertyChangedEventHandler does not work

I have a following label and a slider in my UserControl xaml
<Label x:Name="labelValX" Content="{Binding Path=xValue}" HorizontalAlignment="Left" Width="88" Height="44"/>
<Slider x:Name="sliderSpeed" Value="{Binding slideValue, Mode=TwoWay}" HorizontalAlignment="Left" Margin="10,35,0,0" VerticalAlignment="Top" Width="173" Height="53" Minimum="10" Maximum="100" />
and a specific SetGetAccValues.cs class:
public class SetGetAccValues : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _xval;
public string xValue
{
get { return _xval; }
set
{
if (value != _xval)
{
_xval = value;
OnPropertyChanged("xValue");
}
}
}
public byte _slideValue;
public byte slideValue {
get
{
return _slideValue;
}
set
{
if (value != _slideValue)
{
_slideValue = value;
OnPropertyChanged("slideValue");
}
}
}
protected virtual void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
if (propName.Equals("slideValue"))
{
speedAccMeter(slideValue);
}
}
}
An in my other GetAccNotifications.cs class, I have following part, where I'm defining my xValue string to a specific value:
Y = ((double)(sbyte)value) / 64.0;
Y = Math.Round(Y, 2);
SetGetAccValues set = new SetGetAccValues();
set.xValue = Y.ToString();
The problem occurs when the OnPropertyChanged is triggered with "xValue" as the propName, the PropertyChangedEventHandler remains always null, but when it is triggered with "slideValue" as propName it is actually not null. Why does it remain null in the xValue case ?.
I believe the PropertyChanged event is firing before the datacontext is finished loading.
You can listen to the DataContextChanged event in your usercontrol, so that when the new datacontext is available, you can set your properties.
public AccView()
{
InitializeComponent();
this.DataContextChanged += OnDataContextChanged;
this.DataContext = new SetGetAccValues();
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
Y = ((double)(sbyte)value) / 64.0;
Y = Math.Round(Y, 2);
(dependencyPropertyChangedEventArgs.NewValue as SetGetAccValues).xValue = Y.ToString();
}
I think you didn't bind DataContext. You should set DataContext with code behind in your case.
In SetGetAccValues.xaml.cs
public SetGetAccValues()
{
DataContext = this;
}

INotifyPropertyChanged doesn't work

I have a simple object (which is globally initiated in App.xaml.cs):
public class now_playing : INotifyPropertyChanged
{
// notify
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string p)
{
Debug.WriteLine(p + ": notify propertychanged");
PropertyChangedEventHandler handler = PropertyChanged;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
// artist
public string artist
{
get
{
return _artist;
}
set
{
_artist = value;
NotifyPropertyChanged("artist");
}
}
private string _artist;
// album
public string album
{
get
{
return _album;
}
set
{
_album = value;
NotifyPropertyChanged("album");
}
}
private string _album;
// track title
public string tracktitle
{
get
{
return _tracktitle;
}
set
{
_tracktitle = value;
NotifyPropertyChanged("tracktitle");
}
}
private string _tracktitle;
}
Whenever I change the values, the class does notify (I see the debug).
So I guess the problems lies in my XAML or the code behind.
Page code:
public sealed partial class nowplaying : Page
{
// artistdata
public string artist { get { return App.nowplaying.artist; } }
// albumdata
public string album { get { return App.nowplaying.album; } }
// trackdata
public string tracktitle { get { return App.nowplaying.tracktitle; } }
public nowplaying()
{
this.InitializeComponent();
this.DataContext = this;
}
}
XAML:
<Grid Margin="50">
<TextBlock Text="{Binding tracktitle}" Foreground="White" FontSize="40"/>
<TextBlock Foreground="#dcdcdc" FontSize="20" Margin="0,50,0,0">
<Run Text="{Binding artist}"/>
<Run Text=" - "/>
<Run Text="{Binding album}"/>
</TextBlock>
</Grid>
Why does the UI not update when I change values?
Stack trace:
Music.exe!Music.App.InitializeComponent.AnonymousMethod__6(object sender = {Music.App}, Windows.UI.Xaml.UnhandledExceptionEventArgs e = {Windows.UI.Xaml.UnhandledExceptionEventArgs}) Line 50 C#
Music.exe!play_music.MessageReceivedFromBackground(object sender = null, Windows.Media.Playback.MediaPlayerDataReceivedEventArgs e = {Windows.Media.Playback.MediaPlayerDataReceivedEventArgs}) Line 57 C#
UPDATE: problem solved! I had to use a dispatcher when calling the propertychanged event:
CoreDispatcher dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
if (PropertyChanged != null)
{
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
this.PropertyChanged(this, new PropertyChangedEventArgs(p));
});
}
You "loose" the change notification in the properties in the Page as these properties do not have any change notifiaction.
Try using now_playing directly:
public sealed partial class nowplaying : Page
{
public now_playing NowPlaying { get { return App.nowplaying; } }
public nowplaying()
{
this.InitializeComponent();
this.DataContext = this;
}
}
and
<Run Text="{Binding NowPlaying.artist}"/>
Otherwise you need to implement INotifiyPropertyChanged in nowplaying and forward the events from now_playing.
You actually binding to artist, album and tracktitle Of nowplaying class which does implement INotifyPropertyChanged

Categories