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}"/>
Related
I've created a C# WPF app with a RibbonApplicationMenu displaying a Most Recently Used (MRU) list. Unfortunately the display doesn't update when I select an existing file from the list or upload a new file. In the XAML I have:
<local:MostRecentFiles x:Key="MostRecentFilesData" />
...
<ribbon:RibbonApplicationMenu.AuxiliaryPaneContent>
<ribbon:RibbonGallery Name="RecentDocuments" CanUserFilter="False"
SelectedValue="{Binding MostRecentFile, UpdateSourceTrigger=PropertyChanged}">
<ribbon:RibbonGalleryCategory Header="Recent Documents"
ItemsSource="{DynamicResource MostRecentFilesData}">
</ribbon:RibbonGalleryCategory>
</ribbon:RibbonGallery>
</ribbon:RibbonApplicationMenu.AuxiliaryPaneContent>
The DataContext is set to a class containing
private ObservableCollection<string> _mostRecentFile = new ObservableCollection<string>();
public ObservableCollection<string> MostRecentFile
{
get { return _mostRecentFile; }
set
{
_mostRecentFile = value;
OnPropertyChanged("MostRecentFile");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
In the OpenFile routine the code is
MostRecentFiles mrf = new MostRecentFiles();
mrf.AddMRUitem(openFileDlg.FileName);
The MostRecentFiles class contains the main class methods and I've put some sample file paths in the code.
public class MostRecentFiles : ObservableCollection<string>
{
public ObservableCollection<string> MRUmenuItems = new ObservableCollection<string>();
public MostRecentFiles()
{
AddMRUitem(#"C:\MyDocuments\File3.txt"); //
AddMRUitem(#"C:\MyDocuments\File2.txt"); // } Sample files
AddMRUitem(#"C:\MyDocuments\File1.txt"); //
}
public void AddMRUitem(string filePath)
{
int result;
result = MRUmenuItems.IndexOf(filePath);
if (result != -1)
{
MRUmenuItems.Remove(filePath);
MRUmenuItems.Insert(0, filePath);
}
else
AddMenuItem(filePath);
UpdateMRUList();
}
private void UpdateMRUList()
{
this.Clear();
foreach (string filePath in MRUmenuItems)
{
this.Add(filePath);
}
//OnPropertyChanged("MostRecentFile"); // <= Error CS1503
}
private void AddMenuItem(string newMRUfile)
{
MRUmenuItems.Insert(0, newMRUfile);
if (MRUmenuItems.Count > 10)
{
MRUmenuItems.RemoveAt(MRUmenuItems.Count - 1);
}
}
private string _mostRecentFile = "";
public string MostRecentFile
{
get { return _mostRecentFile; }
set
{
if (_mostRecentFile == value) return;
_mostRecentFile = value;
AddMRUitem(_mostRecentFile);
//OnPropertyChanged("MostRecentFile");
}
}
}
Undeleting OnPropertyChanged in UpdateMRUList() produces the error: Error CS1503 Argument 1: cannot convert from 'string' to 'System.ComponentModel.PropertyChangedEventArgs'
When I launch the program the menu correctly displays the three files but when I select one the displayed order doesn't change; I expect the selected file to move to the top of the list. Similarly when I open a new file the filename isn't added to the MRU.
However if I step through the code the lists are being updated in the correct order. What have I done wrong?
You are binding SelectedValue to a collection. You don't need a custom collection. Just add an ObservableCollection to your view model and move items on selected item changed:
View model:
private void OnSelectedMostRecentFileChanged()
{
// Move the selected item to the front of the list
this.MostRecentFiles.Move(this.MostRecentFiles.IndexOf(this.SelectedRecentFile), 0);
}
private string _selectedRecentFile;
public string SelectedRecentFile
{
get { return _selectedRecentFile; }
set
{
_selectedRecentFile= value;
OnSelectedMostRecentFileChanged();
OnPropertyChanged(nameof(SelectedRecentFile));
}
}
private ObservableCollection<string> _mostRecentFiles = new ObservableCollection<string>();
public ObservableCollection<string> MostRecentFiles
{
get { return _mostRecentFiles; }
set
{
_mostRecentFiles = value;
OnPropertyChanged(nameof(MostRecentFiles));
}
}
View:
<ribbon:RibbonApplicationMenu.AuxiliaryPaneContent>
<ribbon:RibbonGallery Name="RecentDocuments" CanUserFilter="False"
SelectedItem="{Binding SelectedRecentFile}">
<ribbon:RibbonGalleryCategory Header="Recent Documents"
ItemsSource="{Binding MostRecentFiles}">
</ribbon:RibbonGalleryCategory>
</ribbon:RibbonGallery>
</ribbon:RibbonApplicationMenu.AuxiliaryPaneContent>
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);
}
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}" />