I am a student that just finished up a summer internship, and I brought home a project to work on briefly before school starts up. This project has a stopwatch in it, and I would rather use an ObservableCollection bound to my ListBox for my split times, rather that using the listbox.Items.Add(). When I add to the ObservableCollection, the ListBox UI does not update. Could anyone point me in the right direction on what I missed or what I did wrong?
I have my TimeSplits class:
public class TimeSplits : INotifyPropertyChanged
{
private int _hours;
private int _minutes;
private int _seconds;
public int hours
{
get
{
return _hours;
}
set
{
_hours = value;
NotifyPropertyChanged(hours);
}
}
public int minutes
{
get
{
return _minutes;
}
set
{
_minutes = value;
NotifyPropertyChanged(minutes);
}
}
public int seconds
{
get
{
return _seconds;
}
set
{
_seconds = value;
NotifyPropertyChanged(seconds);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(int propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(nameof(propertyName)));
}
}
public override string ToString()
{
return hours.ToString() + ":" + minutes.ToString() + ":" + seconds.ToString();
}
}
and my ObservableCollection in my Page:
public partial class StopwatchPage : Page , INotifyPropertyChanged
{
...
public ObservableCollection<TimeSplits> splits = new ObservableCollection<TimeSplits>();
...
public StopwatchPage()
{
DataContext = this;
InitializeComponent();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += new EventHandler(stopwatchTimer);
}
...
private void splitButton_Click(object sender, RoutedEventArgs e)
{
TimeSplits split = new TimeSplits();
split.hours = Hours;
split.minutes = Minutes;
split.seconds = Seconds;
splits.Add(split);
}
...
}
and my xaml:
<ListBox x:Name="newSplitListBox" HorizontalAlignment="Left" Margin="139,0,0,47" Width="185" Height="268" VerticalAlignment="Bottom" ItemsSource="{Binding splits}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding hours}"/>
<TextBlock Text="{Binding minutes}"/>
<TextBlock Text="{Binding seconds}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I am sure it is something small that I have no clue about, as I just started learning data binding this summer. Any help is greatly appreciated! Thanks in advance.
It looks like you have nameof() in the wrong place. The way your current code reads, it will always send the value of "propertyName" as the name of the property that changed, regardless of what property actually changed.
Try this:
public int hours
{
get
{
return _hours;
}
set
{
_hours = value;
NotifyPropertyChanged();
}
}
Then, in your NotifyPropertyChanged(), do this:
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}
Edit: Added fix for the following:
Also, the ObservableCollection needs to be a property. Change this code:
public ObservableCollection<TimeSplits> splits = new ObservableCollection<TimeSplits>();
To this:
public ObservableCollection<TimeSplits> Splits { get; set; } = new ObservableCollection<TimeSplits>();
I learned a trick from Xamarin's ViewModel template that helped me immensely. Here is the code that it generates that handles an observable View Model (much like the ObservableCollection).
protected bool SetProperty<T>(ref T backingStore, T value,
Action onChanged = null,
[CallerMemberName]string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
Then, to use this, simply add this to your properties:
private string _title = string.Empty;
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
Related
This is the first time I have used WinUI. I have all of the information I need pulling in, but the binding back to the UI to display the data after async functions isn't updating the UI controls.
I have a view model that is handling the streams to load in the string from the user's selected file, and storing that List<string> in the model object. The model object implements INotifyPropertyChanged. I must not understanding completely the relationship between x:bind and INotifyPropertyChanged because nothing is set from the view to the PropertyChanged event.
public class PCCBill : INotifyPropertyChanged
{
private string importFileName;
private List<string> lines;
private string originalFileText;
//private List<string> formattedLines;
public PCCBill(string displayFileName)
{
this.ImportFileName = displayFileName;
}
public string ImportFileName
{
get { return importFileName; }
set
{
this.importFileName = value;
RaisePropertyChanged(nameof(ImportFileName));
}
}
public List<string> Lines
{
get { return lines; }
set
{
this.lines = value;
string txt = "";
foreach (string line in this.lines)
{
txt += line + Environment.NewLine;
}
this.OriginalFileText = txt;
}
}
public string OriginalFileText
{
get { return originalFileText; }
set
{
this.originalFileText = value;
RaisePropertyChanged(nameof(OriginalFileText));
}
}
/*public string FormattedFileText
{
get
{
string txt = "";
foreach(string line in this.formattedLines)
{
txt += line + Environment.NewLine;
}
return txt;
}
}*/
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
I have the x:Bind set in the View with OneWay Mode:
<TextBlock Grid.Row="1" x:Name="FileNameTxtBlock" VerticalAlignment="Center"
Margin="15,0" Text="{x:Bind ViewModel.Bill.ImportFileName, Mode=OneWay}"/>
<TextBlock x:Name="OrgPreviewTxtBlock" Text="{x:Bind ViewModel.Bill.OriginalFileText, Mode=OneWay}"/>
I have played around with this for a while and decided to see if someone can help, I have set in the constructor of StatusInfo the DataContext = this and didn't work. When I write a string to ScreenStatusBarText it does call the OnPropertyChanged method but every time the PropertyChanged value is null. I The status block I have at the bottom of the screen. I have a tab section above this stack panel that has many components that use bindings and work.
Screen Code
<StackPanel Margin="0,1047,0,0">
<Grid Name="StatusBarItemGrid">
<TextBlock Name="StatusBarText" Text="may the force be with you" VerticalAlignment="Center" HorizontalAlignment="Stretch" />
</Grid>
</StackPanel>
Data Model:
public partial class StatusInfo : INotifyPropertyChanged
{
private string screenStatusBarText;
public StatusInfo()
{
BindScreenStatusBarText();
screenStatusBarText = "Initialized";
}
public string ScreenStatusBarText
{
get { return screenStatusBarText; }
set
{
screenStatusBarText = value;
OnPropertyChanged("StatusBarText");
}
}
private void BindScreenStatusBarText()
{
Binding b = new Binding();
b.Source = screenStatusBarText;
b.Mode = BindingMode.OneWay;
b.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
b.Path = new PropertyPath("StatusBarText");
MainWindow.mainWindow.StatusBarText.SetBinding(TextBlock.TextProperty, b);
MainWindow.mainWindow.StatusBarText.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
}
My main :
public partial class MainWindow : Window
{
public static StatusInfo status;
public MainWindow()
{
InitializeComponent();
SourceInitialized += MainWindow_SourceInitialized;
}
private void MainWindow_SourceInitialized(object sender, EventArgs e)
{
SetUpDisplay();
}
private void SetUpDisplay()
{
status = new StatusInfo();
}
}
Set the Binding in XAML instead of code behind:
<TextBlock Text="{Binding ScreenStatusBarText}" />
And use a view model like
public class StatusInfo : INotifyPropertyChanged
{
private string screenStatusBarText = "Initialized";
public string ScreenStatusBarText
{
get { return screenStatusBarText; }
set
{
screenStatusBarText = value;
OnPropertyChanged(nameof(ScreenStatusBarText));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(propertyName));
}
}
with an instance of the view model class assigned to the MainWindow's DataContext:
private readonly StatusInfo statusInfo = new StatusInfo();
public MainWindow()
{
InitializeComponent();
DataContext = statusInfo;
}
You may now access the view model class at any time later, e.g. in an event handler of an element of MainWindow:
statusInfo.ScreenStatusBarText = "Something";
I think your going to struggle doing your binding in code behind.
Having said that, with regards to why your PropertyChanged value is null. You've simply made a typo, as-is you're notifying subscribers that a property that doesn't exist has changed. One solution to avoid such typos is to use nameof.
public string ScreenStatusBarText
{
get { return screenStatusBarText; }
set
{
screenStatusBarText = value;
OnPropertyChanged(nameof(ScreenStatusBarText));
}
}
It occurred to me you may also have meant that your event was null. This simply means you don't have any subscribers. See Why is my "Event" always null?.
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) // I have a subscriber.
handler(this, new PropertyChangedEventArgs(propertyName));
}
I am a bit new on c# WPF.
I have been following MVVM pattern and everything is set, my code seem to work fine but Issue I am facing is when I bind the data on xaml file, the data I am receiving from get set property but binding seems to have gone as no data is displayed on my text box. check my code.
/**********************xaml code***********************************\
<UserControl x:Class="ILS.debugger.debuggermsg"
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"
xmlns:serial="clr-namespace:ILS.VM.Serial_Monitor;assembly=ILS.VM"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Text="{Binding Debugger_Recoreded}" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" Background="#FFEBD3D3">
</TextBox>
</Grid>
</UserControl>
/***********************viewmodel code******************\
namespace ILS.VM.Serial_Monitor
{
public class serial : NotifyPropertyChanged
{
private string debuger_rec;
public string Debugger_Recoreded
{
get { return debuger_rec; }
set
{
if (this.debuger_rec == value)
return;
this.debuger_rec = value;
i--;
if (i == 0)
{
this.debuger_rec = String.Empty;
i = 1000;
}
this.InvokePropertyChanged("Debugger_Recoreded");
}
}
/***********************model******************\
namespace ILS
public void OnDebugger(String Receved_Data) //debug message monitor code
{
try
{
this.serialData.Debugger_Recoreded += " " + DateTime.Now + " " + Receved_Data + Environment.NewLine;
this.serialData.Debugger_Recoreded += Environment.NewLine;
}
catch (Exception e)
{
}
}
public class serial : INotifyPropertyChanged
{
private string debuger_rec;
public string Debugger_Recoreded
{
get { return debuger_rec; }
set
{
if (this.debuger_rec == value)
return;
this.debuger_rec = value;
i--;
if (i == 0)
{
this.debuger_rec = String.Empty;
i = 1000;
}
OnPropertyChanged("Debugger_Recoreded");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
And set DataContext too, in main windows enter this lines:
this.DataContext = serialData;
Also you can use mode way to bind.
<TextBox Text="{Binding Debugger_Recoreded,Mode="Towway"}" />
In your code-behind (i.e your debuggermsg class), you have to instantiate and assign a DataContext:
public debuggermsg()
{
InitializeComponent();
this.DataContext = new serial();
}
It is required for DataBinding, so that you will be able to interact with your ViewModel's properties.
Then, modify your ViewModel like so:
public class serial : INotifyPropertyChanged
{
private string debuger_rec;
public string Debugger_Recoreded
{
get { return debuger_rec; }
set
{
if (this.debuger_rec == value)
return;
this.debuger_rec = value;
i--;
if (i == 0)
{
this.debuger_rec = String.Empty;
i = 1000;
}
OnPropertyChanged("Debugger_Recoreded");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Implementation of OnPropertyChanged method is required to notify your view of a modification of your ViewModel's property.
Everything should be fine then.
When implementing INotifyPropertyChanged it's best to use [CallerMemberName] attribute, it's in the System.Runtime.CompilerServices, as you don't have to hardcode a string name of the calling property:
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now you can write your property like this:
private string debuggerRecorded;
public string DebuggerRecorded
{
get
{
return debuggerRecorded;
}
set
{
if (debuggerRecorded != value)
{
this.debuggerRecorded = value;
i--;
if (i == 0)
{
this.debuggerRecorded = String.Empty;
i = 1000;
}
OnPropertyChanged(); // No argument needed
}
}
}
By doing this you don't have to worry about spelling and you can freely change the names of your properties in the future, you don't have to remember to change it also in the OnPropertyChanged.
Assuming everything else works fine with your code you just need to set DataContext, which is usually done in MainWindow. For example, like this:
public partial class MainWindow : Window
{
private Serial viewModel;
public MainWindow()
{
InitializeComponent();
viewModel = new Serial();
this.DataContext = viewModel;
}
}
Also, you may want to write your text box with another property:
TextBox Text="{Binding DebuggerRecorded, UpdateSourceTrigger=PropertyChanged}" ...
If you omit this last part, the Text will get updated only when the TextBox loses focus.
When i try to update value of inserted element in ListView i didn't see changes on phone, but in debug message i see changes. Here my code.
private void chat_LayoutUpdated(object sender, object e)
{
foreach (Message item in chat.Items)
{
item.MessageTime = GetRelativeDate(item.MessageDateTime);
Debug.WriteLine(item.MessageTime); //Display changed value(Delta computed on step before) but on phone screen value didn't change;
}
}
GetRelativeDate // Long function which return delta between current time and time when message was sended.
class Message // Model of chat message
{
public string MessageText { get; set; }
public string MessageTime { get; set; } // This value i want to change in ListView.
public DateTime MessageDateTime { get; set; }
}
XAML
<TextBlock Grid.Column="0"
FontSize="22"
Text="{Binding MessageText}" />
<TextBlock
Grid.Column="1"
Grid.Row="1"
FontSize="10"
Text="{Binding MessageTime}" />
P/s Maybe i need something more specific for using as chat windows.
Anyway thanks guys i will really appreciate any answers or suggestions.
Need to Implement INofityPropertyChanged
MSDN: inotifypropertychanged Example
Sample from the MSDN article:
public class DemoCustomer : INotifyPropertyChanged
{
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));
}
}
private DemoCustomer()
{
}
private string customerNameValue = String.Empty;
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
NotifyPropertyChanged();
}
}
}
}
Thanks #ChubosaurusSoftware! .
Here how i implement that interface maybe some on need example, or i make not very clear and you can suggest better solution.
At first add to using System.ComponentModel;
Then change model like that.
class Message : INotifyPropertyChanged
{
public string MessageText { get; set; }
private string messageTime = String.Empty;
public string MessageTime
{
get { return this.messageTime; }
set
{
if (value != this.messageTime)
{
this.messageTime = value;
NotifyPropertyChanged("MessageTime");
}
}
}
public DateTime MessageDateTime { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I tried this and it worked for me
List<Model.BeneficiaryItem> oldListItem = listItem;
List<Model.DataType> newListItem = new List<Model.DataType>();
ListView.ItemsSource = newListItem;
//update the data
for (int i = 0; i < oldListItem.Count; i++)
{
Model.DataTyep benItem = new Model.DataTyep ();
// update the individual object benItem
newListItem.Add(benItem);
}
ListViewBenList.ItemsSource = newListItem;
I have two TextBoxes. I have two ObservableCollections. The ObservableCollection has items of the following type:
public class ChartData : INotifyPropertyChanged
{
DateTime _Name;
double _Value;
#region properties
public DateTime Name
{
get
{
return _Name;
}
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
public double Value
{
get
{
return _Value;
}
set
{
_Value = value;
OnPropertyChanged("Value");
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I need to bind each of the TextBox.Text to the Value Property in each of the ObservableCollections. The ObservableCollections are DataContext for other controls in the window too. Since I have more than one ObservableCollection, I cannot set the DataContext to the Window.
New data is added to the ObservableCollection using:
ObservableCollection<ChartData>lineSeries1Data = new ObservableCollection<ChartData>();
lineSeries1Data.Add(new ChartData() { Name = DateTime.Now, Value = 0.0 });
When a new Value is added to the Collection, I want the TextBox to show the Value property
You can try something like this if you don't need a "real" binding, but just need to display the Value of the last object which is added (pseudo code):
public string NewItem { get; set+notify; }
ctor(){
myCollection = new ObservableCollection<T>();
myCollection.CollectionChanged += OnMyCollectionChanged;
}
private void OnMyCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
if (args.Action == NotifyCollectionChangedAction.Add){
var last = args.NewItems.FirstOrDefault();
if (last == null) return;
NewItem = last.Value;
}
}
//XAML:
<TextBox Text="{Binding NewItem, Mode=OneWay}" />