Searching didn't bring me any clues and I am sort of at a loss.
WPF is self taught so far, so I might be overlooking something simple.
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<TextBlock Text={Binding BoundTextProperty}"/>
that's the simplified xml
public class MainViewModel
{
private Model Data;
public MainViewModel()
{...}
public string BoundTextProperty => Data.BoundTextProperty;
...
}
The Property that's bound referencing the Property holding the Data in the model
public class Model : INotifyPropertyChanged
{
private long number;
public long Number
{
get { return number; }
set
{
number = value;
OnPropertyChanged(nameof(BoundTextProperty));
}
}
public string BoundTextProperty => $"Some text {Number} some text again";
public virtual event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I swear it worked at some point.
The string has a couple other variables, but that's the basic of how it works or rather doesn't.
My Question is wether or not the Binding can actually bubble up, and if it can, why doesn't it?
You have to add the code for bubbling up the Model's PropertyChanged event from the ViewModel to the View.
Here is an example (based on your code):
public class MainViewModel : ViewModelBase
{
private readonly Model Data;
public MainViewModel()
{
Data = new Model();
Data.PropertyChanged += ModelOnPropertyChanged;
}
private void ModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(Model.BoundTextProperty):
OnPropertyChanged(nameof(MainViewModel.BoundTextProperty));
break;
// add cases for other properties here:
}
}
public string BoundTextProperty => Data.BoundTextProperty;
}
public class Model : ModelBase
{
private long number;
public long Number
{
get { return number; }
set
{
number = value;
OnPropertyChanged(nameof(BoundTextProperty));
}
}
public string BoundTextProperty => $"Some text {Number} some text again";
}
public abstract class ViewModelBase : Base
{
// add other ViewModel related stuff here
}
public abstract class ModelBase : Base
{
// add other Model related stuff here
}
public abstract class Base : INotifyPropertyChanged
{
public virtual event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
You need to raise the PropertyChanged event for the source property that you bind to in your XAML. In this case you bind to the BoundTextProperty of the MainViewModel which means that the MainViewModel class should raise the PropertyChanged event.
It doesn't matter whether the source property wraps another property of a class that does raise the PropertyChanged event. It's the source object of the binding that notifies the view.
You could also just bind to the "model" property directly, provided that you turn Data into a public property in your view model:
public Model Data { get; private set; }
...
<TextBlock Text="{Binding Data.BoundTextProperty}"/>
If you choose to stick with your wrapper property, the MainViewModel must implement the INotifyPropertyChanged event and raise the PropertyChanged event whenever the model is updated:
public class MainViewModel : INotifyPropertyChanged
{
private readonly Model Data;
public MainViewModel()
{
Data = new Model();
Data.PropertyChanged += Data_PropertyChanged;
}
private void Data_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("BoundTextProperty");
}
public string BoundTextProperty => Data.BoundTextProperty;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Related
Since the page classes in my blazor application are getting somewhat long and untidy and I'm coming originally from WPF development anyway, I thought, I have a look into MVVM-ifying my application.
Preliminaries:
Here is my ViewModelBase
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T storage, T value,
[CallerMemberName] string property = null)
{
if (object.Equals(storage, value)) return;
storage = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
And here is a relevant of the relevant ViewModel and the corresponding interface for the Dependency-Injection system:
public class DatasetManagerViewModel : ViewModelBase, IDatasetManagerViewModel
{
private DatasetModel _selectedDataset;
public DatasetModel SelectedDataset
{
get { return _selectedDataset;}
set { SetProperty(ref _selectedDataset, value); }
}
public ObservableCollection<ImageModel> DatasetFiles { get; set; } = new();
}
public interface IDatasetManagerViewModel
{
event PropertyChangedEventHandler PropertyChanged;
ObservableCollection<ImageModel> DatasetFiles { get; set; }
DatasetModel SelectedDataset { get; set; }
}
In the View/Page Class this is implemented as follows:
[Inject]
protected IDatasetManagerViewModel ViewModel { get; private set; }
private async void OnPropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
await InvokeAsync(() =>
{
StateHasChanged();
});
}
protected override async Task OnInitializedAsync()
{
ViewModel.PropertyChanged += OnPropertyChangedHandler;
await base.OnInitializedAsync();
}
// also unsubscribe event after disposal
Question:
This works fine in case of normal properties such as in case of the above SelectedDataset.
However, I'm wondering what the best way is to bind an ObservableCollection such as DatasetFiles to the view class to incorporate the CollectionChanged event (If I recall correctly this is done automatically in WPF when a GUI element is bound to an ObservableCollection?).
In the meanwhile I changed my ViewModelBase to the following:
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T storage, T value,
[CallerMemberName] string property = null)
{
if (object.Equals(storage, value)) return;
storage = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
protected void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
}
}
Any ViewModel with an ObservableCollection is then responsible to subscribe the added OnCollectionChanged() to the CollectionChanged event.
If anyone comes up with a nicer solution, I'd be interessted :)
3 steps nested string property is not updating the UI. When I update the EvidenceName property it doesnt reflect on the UI right away until I navigate back and come again on this page in which case the viewmodel is initialized again.
I have a xaml page with following code :
<TextBlock Text="{x:Bind ViewModel.SelectedEvidence.EvidenceName, Mode=OneWay}" />
ViewModel property in code behind :
public EvidenceViewModel ViewModel { get; } = new EvidenceViewModel();
Selected Evidence property within the EvidenceViewModel :
public Evidence SelectedEvidence
{
get => _selectedEvidence;
set => Set(ref _selectedEvidence, value); //this calls for RaisePropertyChanged
}
EvidenceViewModel derives from Observable class for raising property changes.
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
EvidenceName property within the Evidence class
public string EvidenceName
{
get { return _evidenceName; }
set
{
if (_evidenceName != value)
{
_evidenceName = value;
RaisePropertyChanged();
}
}
}
Update 1
if I put a simple string property directly in EvidenceViewModel and bind the UI textblock to that string property than the changes reflect in real time as expected.
Update 2
After some further debugging I found out that any property which is being inherited by the class from parent class doesnt work well in binding, so EvidenceName property was actually coming from parent class EvidenceBase and was being inherited into child class Evidence.
Update 3
Code for Evidence class in Nswagger generated file for client
Code for EvidenceBase class
EvidenceName property which actually exists in EvidenceBase class
RasiePropertyChanged code in EvidenceBase
You could to let the Evidence class inherit from the Observable class and call the OnPropertyChanged method in EvidenceName.
For example:
public class Evidence:Observable
{
private string _evidenceName;
public string EvidenceName
{
get { return _evidenceName; }
set
{
if (_evidenceName != value)
{
_evidenceName = value;
OnPropertyChanged("EvidenceName");
}
}
}
}
Update:
I have tested the code from your Update 3, and I found that the problem is the overrides in Evidence class.
Please check the following code:
private void Button_Click(object sender, RoutedEventArgs e)
{
ViewModel.SelectedEvidence.EvidenceName = "testName";
}
public abstract partial class EvidenceBase : System.ComponentModel.INotifyPropertyChanged
{
private string _evidenceName;
[Newtonsoft.Json.JsonProperty("evidenceName",Required =Newtonsoft.Json.Required.Default,NullValueHandling =Newtonsoft.Json.NullValueHandling.Ignore)]
public string EvidenceName
{
get { return _evidenceName; }
set
{
if(_evidenceName!=value)
{
_evidenceName = value;
RaisePropertyChanged("EvidenceName");
}
}
}
protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public partial class Evidence : EvidenceBase, System.ComponentModel.INotifyPropertyChanged
{
//Remove the override of PropertyChanged property and RaisePropertyChanged method to avoid hide the ones inherited from base class.
}
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class EvidenceViewModel:Observable
{
private Evidence _selectedEvidence;
public Evidence SelectedEvidence
{
get { return _selectedEvidence; }
set
{
Set(ref _selectedEvidence, value);
}
}
public EvidenceViewModel()
{
_selectedEvidence = new Evidence();
}
}
If the code cannot state your code about PropertyChanged exactly, please feel free to contact me.
Well, having a go at MVVM with UWP template 10. I have read many pages, and although everyone tries to say its really easy, I still can't make it work.
To put it into context, OCR is being run on an image, and I would like the text to be displayed in textbox automatically.
Here is my Model:
public class TextProcessing
{
private string _ocrText;
public string OcrText
{
get { return _ocrText; }
set
{
_ocrText = value;
}
}
}
Here is my ViewModel:
public class ScanPageViewModel : ViewModelBase, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TextProcessing _ocrTextVM;
public ScanPageViewModel()
{
_ocrTextVM = new TextProcessing();
}
public TextProcessing OcrTextVM
{
get { return _ocrTextVM; }
set {
_ocrTextVM = value;
this.OnPropertyChanged("OcrTextVM");
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Here is my View:
<TextBox x:Name="rtbOcr"
Text="{Binding OcrTextVM.OcrText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Firstly, that is not working. Could someone try to show where I am going wrong?
Then, the data is coming from a Services file, how would the Services update the value? What would be the correct code?
Thanks in advance.
Following code is cite from code.msdn (How to achieve MVVM design patterns in UWP), it will be helpful for you:
Check you code step by step.
1.ViewModel implemented interface INotifyPropertyChanged,and in property set method invoked PropertyChanged, like this:
public sealed class MainPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _productName;
public string ProductName
{
get { return _productName; }
set
{
_productName = value;
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(ProductName)));
}
}
}
}
2.Initialize you ViewMode in you page, and set DataContext as the ViewMode, like this:
public sealed partial class MainPage : Page
{
public MainPageViewModel ViewModel { get; set; } = new MainPageViewModel();
public MainPage()
{
...
this.DataContext = ViewModel;
}
}
3.In you xaml, binding data from viewMode, like this:
<TextBox Text="{Binding Path=ProductName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="ProductNameTextBox" TextChanged="ProductNameTextBox_TextChanged" />
Your OnPropertyChanged call on OcrTextVM isn't actually called in your case, since you set the value in the constructor to its backing field and bypass the property.
If you set the value via the property, it should work:
public ScanPageViewModel()
{
OcrTextVM = new TextProcessing();
}
Of course your view needs to know that ScanPageViewModel is its DataContext. Easiest way to do it is in the constructor of the code-behind of your view:
public OcrView()
{
DataContext = new ScanPageViewModel();
InitializeComponent();
}
Assuming your OCR service is returning a new TextProcessing object on usage, setting the property of OcrTextVM should suffice:
public class ScanPageViewModel : ViewModelBase, INotifyPropertyChanged
{
//...
private void GetOcrFromService()
{
//...
TextProcessing value = OcrService.Get();
OcrTextVM = value;
}
}
On a note, the OcrTextVM name doesn't really reflect what the property is doing, since it doesn't look like it's a viewmodel. Consider renaming it.
Actually, it is very easy once I manage to understand. Here is the code needed to update a TextBox.Text
In the Models:
public class DisplayText : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
}
In the XAML file:
<TextBox Text="{Binding Helper.Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ... />
In the ViewModels:
private DisplayText _helper = new DisplayText();
public DisplayText Helper
{
get { return _helper; }
set
{
_helper = value;
}
}
Then any mod from the ViewModels:
Helper.Text = "Whatever text, or method returning a string";
I have this Base class:
public class MyFileInfo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _file;
private int _bytesSent;
public MyFileInfo(string file)
{
}
public string File
{
get { return _file; }
set { _file = value; }
}
public int BytesSent
{
get { return _bytesSent; }
set
{
_bytesSent = value;
OnPropertyChanged("BytesSent");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And the derive class:
public class MyFile : MyFileInfo
{
public MyFile(MyFileInfo myFileInfo)
}
this.File = pcapInfo.myFileInfo;
this.BytesSent = pcapInfo.BytesSent;
}
public DoWork()
{
// here BytesSent is changing
}
{
OK so i have the base and the derive class.
Inside the derive class my property BytesSent is changing but my UI not.
This is my Collection:
private ObservableCollection<MyFile> files{ get; set; }
Maybe i need to define the OnPropertyChanged method in the derive class ?
ObservableCollection doesn't notify when properties within the items change. It will only notify you when the list it self changes, such as if an item was added or removed.
Look here for more info: ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)
I have the visibility of a progress bar bound to The following property within my viewmodel:
public string CalcProgVisibility
{
get
{
return Calculation.CalcProgVisibility;
}
set
{
}
}
Calculation is my model, which can change the value. When the value changes within the model, what do I need to do to make sure the view is aware of this change?
EDIT:
Here is the property within my model too. I am using onpropertychanged but its not making it to the view.
I am changing the value within the model, the view is bound to my viewmodel and the viewmodel si trying to return a value taken from the model. I am updating the value on the model, and cannot push the fact that it has updated the value all the way down to the view, I can only get the viewmodel to see it has changed...
I updated the entire code. I hope it's clear now.
Define your control BindingMode = TwoWay
<TextBox Visibility="{Binding Path=CalcProgVisibility, Mode=TwoWay}"...
and call the OnPropertyChanged method on the setter of the property in your view model and also in your model
//Model
public class Calculation : INotifyPropertyChanged
{
private string _calcProgVisibility;
public string CalcProgVisibility
{
get { return _calcProgVisibility; }
set
{
_calcProgVisibility = value;
OnPropertyChanged("CalcProgVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
RaisePropertyChanged(propertyName);
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
//ViewModel
public class ViewModel : INotifyPropertyChanged
{
public ViewModel(Calculation model)
{
this.CalcProgVisibility = model.CalcProgVisibility;
model.PropertyChanged += (s, e) => UpdateEntity(s as Calculation);
}
private void UpdateEntity(Calculation source)
{
CalcProgVisibility = source.CalcProgVisibility;
}
private string _calcProgVisibility;
public string CalcProgVisibility
{
get { return _calcProgVisibility; }
set
{
_calcProgVisibility = value;
OnPropertyChanged("CalcProgVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
RaisePropertyChanged(propertyName);
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Your Viewmodel has to implement the INotifyPropertyChanged Interface. To fire it in your case your viewmodel must also be aware of changes in your model object. So your model object could also implement INotifyPropertyChanged, or you use some form of the observer pattern.
If your model implements INotifyPropertyChanged, your viewmodel must manually register for this event and implement an handler. This could in turn trigger the PropertyChange event of the viewmodel then.
Another but in my opinion ugly way would be to scan (per timer or background thread) through your viemodel and check if a value changed since the last scan and then trigger a property changed event.
The first solution could look like this:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace StackOverflow
{
[TestClass]
public class IntegrationTest
{
[TestMethod]
public void NotifyPropertyChangeShouldFireOnViewModelWhenModelChanges()
{
//Arrange
Model model = new Model();
ViewModel sut = new ViewModel(model);
bool notifyPropertyChangeOnViewModelWasCalled = false;
sut.PropertyChanged += (sender, e) => { notifyPropertyChangeOnViewModelWasCalled = true; };
//Act
model.CalcValue = 4711;
//Assert
Assert.IsTrue(notifyPropertyChangeOnViewModelWasCalled, "NotifyPropertyChange was not fired on ViewModel");
}
}
public class ObjectWithNotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Model : ObjectWithNotifyPropertyChanged
{
private double calcValue;
public double CalcValue
{
get
{
return calcValue;
}
set
{
if (calcValue != value)
{
calcValue = value;
RaisePropertyChanged();
}
}
}
}
public class ViewModel : ObjectWithNotifyPropertyChanged
{
public ViewModel(Model model)
{
this.model = model;
model.PropertyChanged += model_PropertyChanged;
}
void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "CalcValue":
RaisePropertyChanged("CalcValue");
break;
}
}
private Model model;
public double CalcValue
{
get
{
return model.CalcValue;
}
}
}
}