To simplify situation. I have MainWindow with two user controls, all of them have corresponding Viewmodels. Everything works fine, properties bind and so on, beside one functionality.
I want to refresh data on second user control after event happened in the first one. Unfortunetly in this scenario, PropertyChanged event (derived from INotifyPropertyChanged, defined in ViewModelBase) is null.
However, if I raise an event from second user control, property on view gets updated as expected!
public class MainWindowViewModel : ViewModelBase
{
public FirstUserControl FirstUserControl {get; set;}
public SecondUserControl SecondUserControl {get; set;}
public MainWindowViewModel ()
{
FirstUserControl =new FirstUserControl();
FirstUserControl.RaiseClicked+=OnRaiseClicked;
SecondUserControl = new SecondUserControl();
SecondUserControl .RaiseClicked+=OnRaiseClicked;
}
private void OnRaiseClicked(object sender, EventArgs e)
{
SecondUserControl.RefreshView();
}
}
public class FirstUserControl : ViewModelBase
{
public ICommand Raise { get; private set; }
public EventHandler RaiseClicked {get;set;}
public FirstUserControl ()
{
Raise = new RelayCommand( p=> RaiseClicked(this, null));
}
}
public class SecondUserControl: ViewModelBase
{
public ICommand Raise { get; private set; }
public EventHandler RaiseClicked {get;set;}
public string Title
{
get
{
return MyLogic.GetCurrentTitle(); // debuggers enter here only while event on second user control raised
}
}
public void RefreshView()
{
OnPropertyChanged("Title"); // debugger enter here in cases
}
}
I suppose there is something with threads going on, but I'm not that familiar with WPF to work out it by my own. Can someone help how to quickly and easy make event from first UC refresh data on the second?
Related
As written everywhere (e.g. here, here, here, here...), reporting an ObservableCollection item property change to a view requires the item to implement INotifyPropertyChanged.
Using CommunityToolkit.Mvvm this can be done using an attribute:
public class MyViewModel : ObservableObject
{
public ObservableCollection<MyItem> MyCollection { get; set; }
//...
}
[INotifyPropertyChanged] // yay! No boilerplate code needed
public partial class MyItem
{
public string MyProperty { get; set; }
}
If somewhere inside MyViewModel there is a change to the MyProperty of an item of MyCollection, the view will be updated.
What if an interface comes into play?
public class MyViewModel : ObservableObject
{
public ObservableCollection<IMyInterface> MyCollection { get; set; }
//...
}
[INotifyPropertyChanged]
public partial class MyItem : IMyInterface // MyProperty is in IMyInterface too
{
public string MyProperty { get; set; }
}
The view seems not to be updated anymore.
I tried:
Inheriting INotifyPropertyChanged in IMyInterface (that requires explicit implementation of the PropertyChanged event and OnPropertyMethod method in MyItem, which I don't want as otherwise I would have not used CommunityToolkit.Mvvm)
Adding [INotifyPropertyChanged] to MyViewModel (expecting nothing but there was an answer somewhere telling that)
Is there an obvious, no-boilerplate solution that I'm missing here?
The view is updated if I do something like suggested here
var item = MyCollection[0];
item.MyProperty = "new value";
MyCollection[0] = item;
but I hope there's a better way.
I'm not sure this will solve your issue but I can see you're using the attribute incorrectly.
Please see
https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/inotifypropertychanged
"These attributes are only meant to be used in cases where the target types cannot just inherit from the equivalent types (eg. from ObservableObject). If that is possible, inheriting is the recommended approach, as it will reduce the binary size by avoiding creating duplicated code into the final assembly."
You're not inheriting from IMyInterface, you're implementing it.
Your viewmodel should inherit ObservableObject
Instead of
[INotifyPropertyChanged]
public partial class MyItem : IMyInterface
You should have:
public partial class MyItem : ObservableObject, IMyInterface
Properties look like:
[ObservableProperty]
private List<FlatTransactionVM> transactions = new List<FlatTransactionVM>();
That generates a public Transactions property.
If you only have properties, you don't need that to be a partial class. That's necessary for relaycommand generation.
EDIT
This works for me:
MainWindow
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding MyCollection}"
DisplayMemberPath="MyProperty"/>
</Grid>
MainWindowViewModel
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private ObservableCollection<IMyInterface> myCollection = new ObservableCollection<IMyInterface>();
public MainWindowViewModel()
{
MyCollection = new ObservableCollection<IMyInterface>(
new List<MyItem>{
new MyItem{ MyProperty = "A" },
new MyItem { MyProperty = "B" },
new MyItem { MyProperty = "C" }
});
}
}
MyItem
public partial class MyItem : ObservableObject, IMyInterface
{
[ObservableProperty]
private string myProperty;
}
Interface
public interface IMyInterface
{
string MyProperty { get; set; }
}
Quick and dirty code in the view. This is purely to see what happens when one of the MyItems.MyProperty is set.
private async void Window_ContentRendered(object sender, EventArgs e)
{
await Task.Delay(5000);
var mw = this.DataContext as MainWindowViewModel;
mw.MyCollection[1].MyProperty = "XXXXX";
}
After a short wait, I see the second item change to XXXXX as expected.
I am trying to binding a number to a Enum located in the view model. I haven't been able to pass the value from the view to the viewmodel through the converter. Is this even possible? I haven't seen anything online that solves this and none of my attemps have worked either.
Viewmodel
public enum TimerOptions
{
FifteenMinutes,
OneHour,
Tomorrow
}
private ICommand _timerCommand;
public ICommand TimerCommand => _timerCommand ??
(_timerCommand = new RelayCommand<TimerOptions>(StartTimer));
private async void StartTimer(TimerOptions option){ .... }
View
<Button Command="{Binding TimerCommand}"
Tag="0"
CommandParameter="{Binding Path=Tag, Converter={StaticResource BidirectionalEnumConverter}}">15 minutes</Button>
In the above example, when the user clicks the button, I want my function to get the first enum value of FifteenMinutes as the parameter. I've tried this by adding a Tag (as seen), adding an x:Name to the Button and also playing around with Source and Path in the binding. Nothing has worked.
Is this possible? I've been trying to solve this for hours, I haven't found anything online.
I want my function to get the first enum value of FifteenMinutes as the parameter.
Why not just do the converter operation in the VM from the info passed in on a string in the command parameter?
Example
<Button Command="{Binding TimerCommand}"
Tag="1"
CommandParameter=1
Then have a ICommand based on that such as
public ICommand TimerCommand =>
new OperationCommand(async (oTag) => await StartTime(oTag).ConfigureAwait(true));
private bool IsOperation { get; set; }
private async void StartTimer(object oTag)
{
int tagId = (int)oTag;
//.... do the convert operations here...
...
// Access VM properties here
IsOperation = true;
await ....;
}
Here is the commanding structure I use instead of Relay.
public class OperationCommand : ICommand
{
#region Variables
private Func<object, bool> CanExecuteHandler { get; set; }
private Action<object> ExecuteActionHandler { get; set; }
public bool InSeparateThread { get; set; }
#endregion
#region Properties
#endregion
#region Construction/Initialization
public OperationCommand(Action<object> executeAction, bool inSeparateThread = false)
{
InSeparateThread = inSeparateThread;
ExecuteActionHandler = executeAction
?? throw new ArgumentNullException(nameof(executeAction));
}
public OperationCommand(Action<object> executeAction,
Func<object, bool> canExecute) : this(executeAction)
{
CanExecuteHandler = canExecute;
}
// Here to adhere to ICommand, change to below if needed
//public event EventHandler CanExecuteChanged;
event EventHandler ICommand.CanExecuteChanged
{
add {}
remove {}
}
#endregion
#region Methods
public bool CanExecute(object parameter) => CanExecuteHandler?.Invoke(parameter) ?? true;
// public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, new EventArgs());
public void Execute(object parameter)
{
ExecuteActionHandler?.Invoke(parameter);
}
#endregion
}
}
I'm having trouble trying to figure out the best way to implement IDataErrorInfo in my WPF application. I have a few models, each with a lot of properties, that are being used. The ViewModel has Properties of each of these models and the View uses these properties to bind to the model. This is a reduced version of the structure I have:
public class ModelA
{
public string PropertyA1 {get; set;}
public string PropertyA2 {get; set;}
}
public class ModelB
{
public string Property B1 {get; set;}
public string Property B2 {get; set;}
}
public class ViewModel
{
public ModelA modelA {get; set;}
public ModelB modelB {get; set;}
}
My questions is - Where do I implement IDataErrorInfo - In the ViewModel or in the Model?
The View binds to these properties as modelA.PropertyA1 and so on so the errors are raised in the Models and not in the ViewModel which makes it necessary to implement IDataErrorInfo in the Models. However, I've heard that it's a good practice to implement validation logic in the ViewModel.
I was wondering if there was a way to catch the errors in the ViewModel without writing a wrapper for each of the properties that would raise an error as there are lots of properties and writing a wrapper for each of them would be tedious.
Thanks for your help!
I think you should implement IDataErrorInfo in the view-model. If you have a base class for your view-models, and you probably should, you can implement it there. Now, all your implementation/validation-logic is in your base class and not spread amongst n-number of view-models.
You want a Valaidation of an Property of an Model this can only be done if you are implement in a Base class or for each Model in the model. If you want to Validate ModelA in ViewModel you need to impliment it in ViewModel but if you want to Implement a Validation for A1 you are need to Implement it in ModelA.
If you change the instance of modelA, IDataViewModel will go into the class and try to call (Instance of your ViewModel)["modelA"]. OK but this is not what you want, if you change a property of modelA IDataViewModel will go into the Instance of modelA and call modelA["B1"], and if you are now implement your validation in ViewModel, modelA will retrun an Empty string
Thank you for your help guys! Here's how I decided to solve my problem:
I created two base classes, one for normal models (Ones that don't have any validations. It only implements INotifyPropertyChanged) and another for models that have validations as shown below.
public abstract class ModelBase : INotifyPropertyChanged
{
//Implement INotifyPropertyChanged here
}
public delegate string ValidateProperty(string propertyName);
public abstract class ValidationModelBase : ModelBase, IDataErrorInfo
{
private bool _canValidate;
public bool CanValidate
{
get { return _canValidate; }
set { _canValidate = value; }
}
#region IDataErrorInfo Members
public string Error
{
get { return string.Empty; }
}
public string this[string columnName]
{
get
{
if (this.CanValidate)
{
return this.Validate(columnName);
}
return string.Empty;
}
}
#endregion
#region Validation Section
public event ValidateProperty OnValidateProperty;
public string Validate(string propertyName)
{
if (this.OnValidateProperty != null)
{
return OnValidateProperty(propertyName);
}
return string.Empty;
}
#endregion
}
Now my models looked like this:
public class ModelA : validationModelBase
{
public string PropertyA1 {get; set;}
public string PropertyA2 {get; set;}
}
public class ModelB : ValidationModelBase
{
public string Property B1 {get; set;}
public string Property B2 {get; set;}
}
Not a huge change there. The ViewModel now looks like this:
public class ViewModel
{
public ModelA modelA {get; set;}
public ModelB modelB {get; set;}
public ViewModel()
{
this.modelA.OnValidateProperty += new ValidateProperty(ValidateModelA);
this.modelB.OnValidateProperty += new ValidateProperty(ValidateModelB);
}
private string ValidateModelA(string propertyName)
{
//Implement validation logic for ModelA here
}
private string ValidateModelB(string propertyName)
{
//Implement validation logic for ModelB here
}
}
This seems to be working for me so far. This way, any new models that have validation need only derive from ValidationModelBase and have the ViewModel add an event handler for the validation event.
If anyone has a better way of solving my problem, do let me know - I'm open to suggestions and improvements.
Ok I'll make this very simple! Here are viewmodels :
public class ObjectsModel
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _objectName;
public string ObjectName
{
get
{
return _objectName;
}
set
{
if (value != _objectName)
{
_objectName = value;
PropertyChanged(this, new PropertyChangedEventArgs("ObjectName"));
}
}
}
public IEnumerable<Object> Objects {get;set;}
public ICommand AddCommand { get; private set; }
public ICommand SaveChangesCommand { get; private set; }
myDomainContext context = new myDomainContext();
public ObjectsModel()
{
objects = context.Objects;
context.Load(context.GetObjectsQuery());
}
}
public class InventoryModel
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public IEnumerable<Inventory> Inventories {get;set;}
public ICommand AddCommand { get; private set; }
public ICommand SaveChangesCommand { get; private set; }
myDomainContext context = new myDomainContext();
public ObjectsModel()
{
objects = context.Objects;
context.Load(context.GetObjectsQuery());
}
}
So what I'm trying to do is in my second form where I want to add an inventory for an object, I have to select the object in a combobox. The question is, how do I fill my combobox? Create another instance of the "ObjectsModel" in the InventoryModel? or use another "context" where I would query the other table? Or is there an easier Xaml approach? If I'm not clear, tell me I'll put more examples/code.
tx a lot!
You want to bind the contents of the combobox to a list of items provided by your ViewModel and bind the selected item to another property on the same ViewModel.
Please get into the habit of naming actual view models to end in "ViewModel", rather than "Model", so they do not clash with your other "real" models. It actually looks like you are binding directly to your business models instead of ViewModels (which is not good).
I would like that my UIElement control acts like a LinkButton in ASP.Net when clicked, and sends an CommandArgument on MouseLeftButtonUp. I suppose I need to make a custom event for this, so I created a OnCommand event like this:
public delegate void OnCommand(object sender, CommandEventArgs e);
public class CommandEventArgs : EventArgs
{
public string CommandArgument { get; set; }
}
How can I add this event to MouseLeftButtonUp on my UIElement, and also pass the CommandArgument? Or is there another way to accomplish the effect of a LinkButton?
I didnt figure out a solution with custom events, so I made a UserControl instead, with a CommandArgument and CommandName properties.
public partial class LinkButton : UserControl
{
public string CommandArgument { get; set; }
public string CommandName { get; set; }
public LinkButton()
{
InitializeComponent();
CommandArgument = String.Empty;
CommandName = String.Empty;
}
public void AddElemnent(UIElement obj)
{
LayoutRoot.Children.Add(obj);
}
}