Bind INotifyPropertyChanged class as Property in another class - c#

I have a class that has a fairly long running process that I want the GUI to give progress on.
The class has a property called Progress that is a class implementing INotifyPropertyChanged. I'm using a BusyWorker and bind the class's Progress property to it's datacontext, but whenever the progress changes the BusyWorker does not show anything. I don't know if I'm making any sense here, so here's some code:
The class in question:
public class MyClass
{
public Progress MyProgress { get; set; }
public void Run()
{
MyProgress = new Progress();
MyProgress.Status = "Initialising";
// Do stuff, update progress, etc.
}
}
public class Progress : INotifyPropertyChanged
{
private string status;
public string Status
{
get { return status; }
set
{
status = value;
OnPropertyChanged("Status");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
XAML:
// ...
<xctk:BusyIndicator HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="busyIndicator" VerticalAlignment="Stretch" BusyContent="{Binding}">
<xctk:BusyIndicator.BusyContentTemplate>
<DataTemplate>
<StackPanel Margin="4">
<TextBlock Text="{Binding Status}" FontWeight="Bold" HorizontalAlignment="Left"/>
</StackPanel>
</DataTemplate>
</xctk:BusyIndicator.BusyContentTemplate>
</xctk:BusyIndicator>
// ...
XAML.CS:
MyClass test = new MyClass();
BusyIndicator.DataContext = test.MyProgress;
BusyIndicator.IsBusy = true;
test.Run();
If I run it like this, and stop at the OnPropertyChanged call, PropertChanged is always null. If I make a separate Progress object in my xaml.cs it works just fine, but I want my 'Run' method to handle this. Is this even possible?

The Problem is you are assigning Data Context before calling the run method which means by the time you are assigning data context "MyProgress" Object is "Null".. so data context is null before calling the "Run" method.. you are calling the Run method which creates an instance for "MyProgress" but since your "MyClass" is not "INotifyPropertyChanged" its not able to notify the data context change...
Solution is: Try creating MyProgress instance in the constructor of MyClass.. so by the time of assigning data context will not be null and the in the run method don't create any instance just update the status property..
Something like this
public class MyClass
{
public Progress MyProgress { get; set; }
public MyClass()
{
MyProgress = new Progress();
}
public void Run()
{
MyProgress.Status = "Initialising";
// Do stuff, update progress, etc.
}
}

<xctk:BusyIndicator HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="busyIndicator" VerticalAlignment="Stretch" BusyContent="{Binding}">
<xctk:BusyIndicator.BusyContentTemplate>
<DataTemplate>
<StackPanel Margin="4">
<TextBlock Text="{Binding Path=Status}" FontWeight="Bold" HorizontalAlignment="Left" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType=BusyIndicator}}"/>
</StackPanel>
</DataTemplate>
</xctk:BusyIndicator.BusyContentTemplate>
// ...

This works for me:
*.xaml
<xctk:BusyIndicator HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="busyIndicator" VerticalAlignment="Stretch" IsBusy="True">
<xctk:BusyIndicator.BusyContentTemplate>
<DataTemplate>
<StackPanel Margin="4">
<TextBlock Text="{Binding Path=Test.MyProgress.Status, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" FontWeight="Bold" HorizontalAlignment="Left"/>
</StackPanel>
</DataTemplate>
</xctk:BusyIndicator.BusyContentTemplate>
</xctk:BusyIndicator>
*.xaml.cs:
public partial class MainWindow : Window
{
public MyClass Test { get; set; }
public MainWindow()
{
Test = new MyClass();
InitializeComponent();
Test.Run();
}
}
public class MyClass
{
public Progress MyProgress { get; set; }
public void Run()
{
MyProgress = new Progress();
MyProgress.Status = "Initialising";
// Do stuff, update progress, etc.
}
}
public class Progress : INotifyPropertyChanged
{
private string status;
public string Status
{
get { return status; }
set
{
status = value;
OnPropertyChanged("Status");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}

Related

C#/WPF Retrieve textbox input value

In my app, I am trying to get the input value of a textbox for use later but I'm unable to successfully get the value. Any ideas where I have made a mistake or how to fix it? Below is my code, if more code is needed, feel free to ask. Thank you in advance.
View Model:
namespace Creator.ViewModels
{
public class CreatureCreatorViewModel : ViewModelBase, INotifyPropertyChanged
{
public CreatureModel CreateNewCreature { get; set; }
/*Variables to "get" input data*/
private string creatureName;
public string CreatureName
{
get {
return creatureName;
}
set
{
if(!string.Equals(creatureName, value))
{
creatureName = value;
OnPropertyChanged("CreatureName");
}
}
}
public CreatureCreatorViewModel(NavigationStore navigationStore)
{
CreateNewCreature = new CreatureModel().NewCreature(creatureName);
}
}
}
View:
<TextBox x:Name="CreatureNameBox" Text="{Binding Path=CreatureName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" TextWrapping="NoWrap" FontFamily="Century Gothic" FontSize="16" Margin="150,0,150,16" MaxLines="1" Height="26" HorizontalAlignment="Stretch" VerticalAlignment="Center" Padding="5,3,5,3" MaxLength="100"/>
ViewModelBase:
namespace Creator.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged, IDisposable
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public virtual void Dispose() { }
}
}
I did a simple test using the source code you provided. And I could see that it's normal as shown in the image below.
I put the sample on GitHub to compare with your source code.
Here
<TextBox Text="{Binding Path=CreatureName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<TextBlock Text="{Binding CreatureName}"/>

Clearing textboxes

I'm making an input page and I'm trying to implement a reset button. After a click on the button, the UI should be empty again.
I thought that entering an empty string would deal with this. In the code it seems to work and the value does get changed to "" but in the UI the typed text stays visible (so it doesn't show the empty "" string). I also tried with string.Empty as suggested in here but that also doesn't seem to work.
Am I missing something here? I'm kinda new to programming so if I did something horribly wrong, don't laugh too hard ;)
I'm using an MVVM pattern and Fody Weaver to deal with the property changed part of the code.
The UI / XAML
<TextBlock Text="Naam:"
Grid.Column="0"
Style="{StaticResource InputInputBlock}"
/>
<TextBox Foreground="White"
Grid.Column="1"
Text="{Binding Name, Mode=TwoWay}"
Style="{StaticResource InputInputBox}"
/>
<Button Content="Reset"
Height="50"
Width="150"
Grid.Column="0"
Grid.Row="2"
VerticalAlignment="Top"
HorizontalAlignment="Center"
Style="{StaticResource FlatButton}"
Command="{Binding ResetCommand}"
/>
The view model
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
}
}
public AddStakeholderViewModel()
{
ResetCommand = new RelayCommand(() => ResetForm());
}
private void ResetForm()
{
Name = " ";
}
You can implement the INotifyPropertyChanged interface in your class. This works for me:
public class Person : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Name");
}
}
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
XAML:
<TextBox Foreground="White"
Grid.Column="1"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Style="{StaticResource InputInputBox}"
/>
MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = newPerson;
}
Person newPerson = new Person();
private void button_Click(object sender, RoutedEventArgs e)
{
newPerson.Name = "";
}
}

ListView: Edit and save SelectedItem with a Button only

I have a ListView that is bound on an ObservableCollection.
<ListView Grid.Column="0" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="0" Margin="5" Name="CustomerListView" ItemsSource="{Binding Customers}" SelectedItem="{Binding Path=CurrentCustomer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Margin="5,0,0,0" Text="{Binding LastName}"/>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the same View i have some TextBoxes which are meant to edit the CurrentCustomer. I also have a save button. If you click this button the modifications of the CurrentCustomer should be saved. If the button "cancel" is pressed the modifications should be discarded.
<TextBox Name="CustomerSalutationTextBox" Grid.Column="1" Grid.Row="0" Height="20px" Margin="5" Text="{Binding Path=CurrentCustomer.Salutation, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The Problem is, if i make some changes on the currentCusomer, they are taking effect immediately.
Do you have a solution?
What you need to add in your ViewModel / the class you have a binding context to is to save what was previous in the Textfield.
And when you hit abort, u just overwrite your newValue with the old one.
I'm going to setup a small example.
class ExampleViewModel : INotifyPropertyChanged {
private string _customerLastName;
private string _customerName;
private string _initialCustomerName;
private string _initialCustomerLastName;
public string CustomerName {
get { return this._customerName; }
set {
this._customerName = value;
this.OnPropertyChanged();
}
}
public string CustomerLastName {
get { return this._customerLastName; }
set {
this._customerLastName = value;
this.OnPropertyChanged();
}
}
public ExampleViewModel(string customerName, string customerLastName) {
this.CustomerName = customerName;
this.CustomerLastName = customerLastName;
this._initialCustomerName = customerName;
this._initialCustomerLastName = customerLastName;
}
//example event handler for your abort button
private void OnAbortButtonClick(object sender, EventArgs args) {
this.CustomerName = this._initialCustomerName; //set the initial name
this.CustomerLastName = this._initialCustomerLastName; //set the initial lastName
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Alternative
As you might load your data from a database/csv file/something else, you should know the original values. When pressing the cancel button, you could invoke a CancelButtonClicked event in your ViewModel and some other class which subscribed to the ViewModels event and knows the original Model could set the original values on that viewModel instance, or just exchange the ViewModel instance with the original one.
Have a look at : https://msdn.microsoft.com/en-us/library/hh848246.aspx
class ExampleViewModel : INotifyPropertyChanged {
private string _customerLastName;
private string _customerName;
public event CancelButtonClicked CancelButtonClicked;
public string CustomerName {
get { return this._customerName; }
set {
this._customerName = value;
this.OnPropertyChanged();
}
}
public string CustomerLastName {
get { return this._customerLastName; }
set {
this._customerLastName = value;
this.OnPropertyChanged();
}
}
public ExampleViewModel(string customerName, string customerLastName) {
this.CustomerName = customerName;
this.CustomerLastName = customerLastName;
}
private void OnAbortButtonClick(object sender, EventArgs args) {
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
internal delegate void CancelButtonClicked(object sender);
public class SomeOtherClass {
private ExampleViewModel _viewModel;
public SomeOtherClass() {
this._viewModel = new ExampleViewModel("foo", "bar");
this._viewModel.CancelButtonClicked += ViewModelOnCancelButtonClicked;
}
private void ViewModelOnCancelButtonClicked(object sender) {
ExampleViewModel vm = sender as ExampleViewModel;
vm.CustomerName = "foo"; //set the initial values again
vm.CustomerLastName = "bar";
}
}
Alternative2
You could also exchange the complete VM when the event of the cancel button is invoked to retreive its original state.
Alternative3
Everytime your SelectedItem changes, you could save the current state of it by creating a copy of it. When your CancelButton is pressed, you set the SelectedItem to the copy of your original viewModel.
You'd need a copy constructor or a copy method for that purpose.
I've found out another solution. In the code behind of the view i've added following:
void saveButton_Click(object sender, RoutedEventArgs e)
{
BindingExpression be = customerFirstNameTextBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
My textbox with UpdateSourceTrigger Explicit
<TextBox Name="customerFirstNameTextBox" Grid.Column="1" Grid.Row="2" Height="20px" Margin="5" Text="{Binding Path=CurrentCustomer.FirstName, Mode=TwoWay, UpdateSourceTrigger=Explicit}" IsEnabled="{Binding Path=IsCustomerTextEnabled}"/>
And my button
<Button Name="SaveButton" Click="saveButton_Click" Margin="5" Content="Save"/>

How to update TextBlock text inside of ListBox item

So I have a simple UDP chat app from a WinForm project, which I wanted to look a little bit better, so I am re-making it in WPF. As I realized I can easily put 2 or more TextBlocks inside of a ListItem, I wanted to display the last message of each chat, like so:
But I have no Idea on how to edit those TextBlocks :( I literary just started with WPF, so I bet I just made a duplicate, but because of that, I don't even know how to search for this issue.
Here is the custom ListBox:
<ListBox x:Name="myList" HorizontalAlignment="Left" Width="264" ScrollViewer.VerticalScrollBarVisibility="Hidden" ScrollViewer.HorizontalScrollBarVisibility="Disabled" BorderThickness="0,1,1,0" MouseLeftButtonUp="myList_MouseLeftButtonUp" Margin="0,25,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="0,0,0,1" Width="250">
<DockPanel Margin="0,7">
<Ellipse Name="ellipse" Margin="5" DockPanel.Dock="Left" Style="{DynamicResource elstyle}">
</Ellipse>
<TextBlock Text="{Binding Name}" DockPanel.Dock="Top" Margin="0,0,0,7" FontWeight="Bold" MaxWidth="250"></TextBlock>
<TextBlock Text="{Binding ID}" DockPanel.Dock="Top" Visibility="Hidden" FontSize="1.333"></TextBlock>
<TextBlock x:Name="last_message" Text="{Binding LastMessage}" DockPanel.Dock="Bottom" MaxWidth="250"></TextBlock>
</DockPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is simplified model to show the principal but if you would create view model class that implement INotifyPropertyChanged interface to hold your item data
public class MyItem : INotifyPropertyChanged
{
private string _name;
private string _id;
private string _lastMessage;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public string ID
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("ID");
}
}
public string LastMessage
{
get { return _lastMessage; }
set
{
_lastMessage = value;
OnPropertyChanged("LastMessage");
}
}
}
and then in your window
public partial class MainWindow : Window
{
private readonly ObservableCollection<MyItem> _myItems = new ObservableCollection<MyItem>();
public MainWindow()
{
InitializeComponent();
myList.ItemsSource = _myItems;
_myItems.Add(new MyItem { Name = "name", ID = "id", LastMessage = "last message" });
_myItems[0].LastMessage = "new message";
}
}
and then you don't operate on myList control anymore but on _myItems list and its items. If you add/remove item in the collection it will add/remove item in the UI, if you change property of an item it will update bound property in the UI

Trouble Binding class to grid, second place

So I'm trying to create my own music player.
I have a Listview updating properly with current playlist.
My problem begins when I want a grid to show the song which is currently playing.
When binding to those strings they all return Null.
From Class:
public class Song : INotifyPropertyChanged
{
public string Title { get; set; }
public string Path { get; set; }
public string Artist { get; set; }
public int Time { get; set; }
public string Album { get; set; }
//public ImageBrush Portrait { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public Song(string title, string path, string artist, int time, string album)//, ImageBrush portrait)
{
this.Path = path;
this.Title = title;
this.Artist = artist;
this.Time = time;
this.Album = album;
//this.Portrait = portrait;
}
public string SongCurrentPlaying
{
get { return Title; }
set
{
Title = value;
OnPropertyChanged("SongCurrentPlaying");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(Title));
}
}
From XAML:
<Grid HorizontalAlignment="Left" Height="143" VerticalAlignment="Top" Width="276">
<Image HorizontalAlignment="Left" Height="124" Margin="10,10,0,0" VerticalAlignment="Top" Width="134" Stretch="Fill"/>
<TextBlock HorizontalAlignment="Left" Margin="144,100,0,0" TextWrapping="Wrap" Text="{Binding Songs.Title}" VerticalAlignment="Top" Height="25" Width="122"/>
<TextBlock HorizontalAlignment="Left" Margin="144,40,0,0" TextWrapping="Wrap" Text="{Binding SongCurrentPlaying.Title, PresentationTraceSources.TraceLevel=High}" VerticalAlignment="Top" Height="25" Width="122"/>
<TextBlock HorizontalAlignment="Left" Margin="144,70,0,0" TextWrapping="Wrap" Text="{Binding Song.Title}" VerticalAlignment="Top" Height="25" Width="122"/>
<TextBlock HorizontalAlignment="Left" Margin="144,10,0,0" TextWrapping="Wrap" Text="{Binding Songs.Title}" VerticalAlignment="Top" Height="25" Width="122"/>
</Grid>
As you can see I've been experimenting on different bindings to get values from Title as example, none has been successful.
Your class structure does not make much sense. You have a Song class that holds a Song's information. However, that class also holds SongCurrentPlaying. Now, if you mean to have SongCurentPlaying on the same class as Song then I would narrow down your issue to how you have performed the XAML bindings. I would assume that the DataContext of your control has not been set to an instance of Song. e.g.
public partial class MainWindow : Window
{
public MainWindow()
{
var someSong = new Song(...);
this.DataContext = someSong;
InitializeComponent();
}
}
Once set as the DataContext you may then bind to the class' properties directly (e.g. {Binding Title}).
Your song class does not need to implement INotifyPropertyChanged. You should think of your songs as models. Instead you should be making a viewmodel for your music player that will hold all your information for your view such as in this case CurrentSongPlaying.
I would make a viewmodel that looks like this
internal class SongPlayerViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Song _songPlaying;
public Song SongPlaying
{
get { return _songPlaying; }
set {
_songPlaying = value;
OnPropertyChanged("SongPlaying");
}
}
protected void OnPropertyChanged(string name)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Then make sure to set the datacontext to an instance of this in the view and inside of the xaml just bind the the title with code like this.
datacontext setup
private readonly SongPlayerViewModel _viewModel = new SongPlayerViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _viewModel;
}
example xaml binding for current song playing
<TextBlock text="{Binding SongPlaying.Title}"/>
If you want something when no song is playing look into Fallbackvalue

Categories

Resources