Another problem has been bugging me lately, and I am quite sure it is my lacking WPF/MVVM skills that prevents me from seing the obvious. I am sure the solution is a simple one, however, I am unsure how to implement it.
I am developing in WPF/C# and I am using the MVVM designpattern.
For simplicity, I am boiling the question down to it's most basic components.
The scenario:
I have a Model, only containing an integer.
I have a Parent ViewModel, displaying this integer.
I have two child viewmodels, incorparated in the parent viewmodel, both displaying this integer. On one of the viewmodel, I have a Command, incrementing the value of the integer with 1. The value is changed in the model, which implements the INotifyPropertyChanged therefore making the second ViewModel aware of the change, so it can update accordingly.
So far everything works fine.
However, I am interested in a new feature, and I cannot get it to work. Say, that I on my second viewmodel want to display the integer, but I want to transform the way the data is displayed. This should be done, however, without changing the data in the model. Should the data in the model change, the transformed data will change accordingly.
As an example, lets assume that the integer is 5. On the second viewmodel, I want to display the integer + 2, meaning 7.
Then the data is changed to 6 from the first viewmodel, meaning that the property on the second viewmodel changes to 8 automatically.
How is this implemented?
A few codepieces, to illustrate what the system looks like so far:
The Model:
public DataModel()
{
data = new Data();
data.Value = 2;
}
public Data data { get; set; }
And the data Class:
public class Data : INotifyPropertyChanged
{
private int m_Value;
public int Value
{
get { return m_Value; }
set
{
if (m_Value != value)
{
m_Value = value;
OnPropertyChanged("Value");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
The mainviewmodel
public class MainViewModel : ViewModelBase
{
readonly DataModel _dataModel;
public MainViewModel()
{
_dataModel = new DataModel();
ViewModel1 = new 1ViewModel(this);
ViewModel2 = new 2ViewModel(this);
}
public 1ViewModel ViewModel1 { get; set; }
public 2ViewModel ViewModel2 { get; set; }
public Data Data
{
get { return _dataModel.data; }
}
}
And here is the way the ChildViewmodels binds themselves to the Data object
public class 1ViewModel : ViewModelBase
{
private MainViewModel _mainViewModel;
public 1ViewModel(MainViewModel mainViewModel)
{
_mainViewModel = mainViewModel;
}
public Data Number
{
get { return _mainViewModel.data; }
}
}
And on the view1, i have bound the Number property like this
<TextBlock Text="{Binding Path=Number.Value}" />
Again, I want to be able to create a second property on the viewmodel, which displays the transformed data, based on, but without changing the original data, and which updates together with the data being updated.
Preferably, it should be a kind of converter-method, which converts the data to the new data.
Hope you are able to help.
The ViewModel should hold the data ready for display but not knowledgeable on the way in which it will be displayed. If you want to change the way in which the data is displayed in your View, for that you would use a Converter. This would allow you to use the same ViewModel for multiple Views and have different appearances.
In addition wrapping a ViewModel inside a ViewModel is not necessarily the way you want to go. A ViewModel generally has an associated View. If you have no view for the ViewModel, treat the data as typical classes and proceed with your single ViewModel.
Use an IValueConverter:
public class AddConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (int)value + (int)parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (int)value - (int)parameter;
}
}
And in your XAML, add the resource:
And update your binding:
I used a ConverterParameter here, but you can hardcode the value if so desired. Might want to add some checks to your converter as well, as it will throw an exception if the types are not correct.
Related
I have a ComboBox bound to a ViewModel string Quality_SelectedItem.
And I have a Method named Quality, which inside accesses the value of the SelectedItem in an if statement.
I have two ways of accessing the value, by passing the ViewModel through the Method, or by passing the string Quality_SelectedItem.
Which way should I be using it and which performs faster?
XAML
<ComboBox x:Name="cboQuality"
ItemsSource="{Binding Quality_Items}"
SelectedItem="{Binding Quality_SelectedItem, Mode=TwoWay}"
HorizontalAlignment="Left"
Margin="0,2,0,0"
VerticalAlignment="Top"
Width="105"
Height="22"/>
ViewModel Class
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void OnPropertyChanged(string prop)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(prop));
}
}
// Quality Selected Item
private string _Quality_SelectedItem { get; set; }
public string Quality_SelectedItem
{
get { return _Quality_SelectedItem; }
set
{
if (_Quality_SelectedItem == value)
{
return;
}
_Quality_SelectedItem = value;
OnPropertyChanged("Quality_SelectedItem");
}
}
...
Example 1 - Passing ViewModel
In the Quality Method, I access vm.Quality_SelectedItem directly from the if statement.
public ViewModel vm = new ViewModel();
// MainWindow
public MainWindow()
{
InitializeComponent();
DataContext = vm;
// Quality Method
Quality(vm); // <---
}
// Quality Method
public static void Quality(ViewModel vm)
{
if (vm.Quality_SelectedItem == "High")
{
// do something
}
else if (vm.Quality_SelectedItem == "Low")
{
// do something
}
}
Example 2 - Passing String SelectedItem
I pass vm.Quality_SelectedItem through the Quality Method and give it the string name quality.
public ViewModel vm = new ViewModel();
// MainWindow
public MainWindow()
{
InitializeComponent();
DataContext = vm;
// Quality Method
Quality(vm.Quality_SelectedItem); // <---
}
// Quality Method
public static void Quality(string quality)
{
if (quality == "High")
{
// do something
}
else if (quality == "Low")
{
// do something
}
}
As a general rule, you should make your code as simple as possible. Remember the KISS principle. This also plays well with SOLID ("simple" is a good way to achieve Single responsibility and Interface segregation).
Avoid reaching into one object to get another.
If you need only a string value in the method, only pass that string value. Don't force your method to dig into object hierarchies and dependencies to get that value.
If the method needs to modify a string property value, then pass the object where to modify the property.
From the performance point of view, you will not notice any change. Accessing an object by-reference is a very cheap operation. (Unless you're implementing loops with billions of iterations.)
From the design point of view, keeping the things simple makes your code SOLID and easily allows re-usage.
It depends on what is //do something.
If you have to handle/interact your viewmodel-object, then pass
viewmodel as parameter
otherwise use a string for less dependencies and possibility of common use.
If you have your viewmodel as singleton, then it doesn't matter which way you go.
Edit:
Turns out I was merging a list with the latest data from a rest API with the data in my GUI. All I really had to do was clear and refill my observablecollection. This post is basically an xy problem. I did not have the vocabulary to explain the problem I was facing.
I'm building an app with a Data class where I store all my data. In this class I have a List filled with objects. I have a Page with a ObservableCollection and a ListView. Currently when I update the ObservableCollection, I clear it and refill it with all the data from Data-class's List. When I do this, my ListView flickers. My guess is that completely rebuilding the ObservableCollection causes this, in combination with a custom ViewCell that is not the lightests. How could I go about updating only what I want? The list/o.collection can have diffrent sizes. The list/o.collection both store the same object.
What I tried:
List<csharp.Truck> favs = data.getFavoriteTrucks();
trucks.Clear();
foreach (csharp.Truck truck in favs)
{
trucks.Add(truck);
}
}
Works but makes my ListView flicker.
Trying this now, its pretty bad code I think, it does update the list how I want it to but the listview does not get updated for some reason. Maybe I need to trigger a refresh?
List<csharp.Truck> all = data.getTrucks();
//if list sizes are not equal(excess/missing objects)
if (all.Count != trucks.Count)
{
//excess object
if (all.Count < trucks.Count)
{
trucks.Clear();
foreach(csharp.Truck t in all)
{
trucks.Add(t);
}
}
//object missing
if(all.Count > trucks.Count)
{
foreach(csharp.Truck t in all)
{
if (!trucks.Contains(t))
{
trucks.Add(t);
}
}
}
}//objects are now the same
//test if object is present but outdated(favorite property)
foreach(csharp.Truck t in trucks)
{
if (t.isFavorite() != all[all.IndexOf(t)].isFavorite())
{
t.setFavorite(all[all.IndexOf(t)].isFavorite());
}
}
Also let me know if this approach is not good practise in the first place.
If you want to update only some properties, you could implement in your model class INotifyPropertyChanged interface.
public class Model : INotifyPropertyChanged
{
private string _myProperty;
public string MyProperty
{
get { return _myProperty; }
set
{
_myProperty = value;
RaisePropertyChanged();
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName]string propertyName = "")
{
Volatile.Read(ref PropertyChanged)?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And then if this property is binded to view, view will know, when you change it:
<Label Text="{Binding MyProperty}" />
I know, title is a little confusing so let me explain. I have a user control that has a dependency property. I access this dependency property with a regular property called Input. In my view model I also have a property called Input. I have these two properties bound together in XAML using two-way binding as shown below:
<uc:rdtDisplay x:Name="rdtDisplay" Input="{Binding Input, Mode=TwoWay}" Line1="{Binding myRdt.Line1}" Line2="{Binding myRdt.Line2}" Height="175" Width="99" Canvas.Left="627" Canvas.Top="10"/>
Okay in my view model, I call a method whenever the value of Input is changed as shown in my property:
public string Input
{
get
{
return input;
}
set
{
input = value;
InputChanged();
}
}
The problem with this is that when I set the value of Input in my view model it only updates the value of the variable input as per my setter in my property. How can I get this to update back to the dependency property in the user control? If I leave the code input = value; out then I get a compilation error.
I need something like this:
public string Input
{
get
{
return UserControl.Input;
}
set
{
UserControl.Input = value;
InputChanged();
}
}
If I make the Input property in my view model look like this:
public string Input
{
get; set;
}
then it works, however, I am unable to call the InputChanged() method that I need to call when the Property is changed. All suggestions are appreciated.
Implement INotifyPropertyChanged in your ViewModel
public class Sample : INotifyPropertyChanged
{
private string input = string.Empty;
public string Input
{
get
{
return input;
}
set
{
input = value;
NotifyPropertyChanged("Input");
InputChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
In your case, you can do it in the code behind of your usercontrol
As the ViewModel has the job to "prepare" the Model's properties to get displayed in the View, what is the best way of referring to the underlying Models properties from the ViewModel?
I could think about two solutions by now:
Option 1 - Duplicate the Model's properties in the ViewModel (wrapper-approach)
Architecture
class Model
{
public string p1 { get; set; }
public int p2 { get; set; }
}
class ViewModel : INotifyPropertyChanged
{
// Model-instance for this ViewModel
private Model M;
public string p1
{
get { return M.p1; }
set
{
M.p1 = value;
// assuming View controls are bound to the ViewModel's properties
RaisePropertyChanged("p1");
}
}
// let's say, I only want to check a Checkbox in the View,
// if the value of p2 exceeds 10.
// Raising the property changed notification would get handled
// in the modifying code instead of the missing setter of this property.
public bool p2
{
get
{
if (M.p2 > 10)
{ return true; }
else
{ return false; }
}
}
// Initialize the Model of the ViewModel instance in its c'tor
public ViewModel()
{ M = new Model(); }
}
Binding
<Textbox Text="{Binding p1}"/>
<Checkbox IsEnabled="False" IsChecked="{Binding p2, Mode=OneWay}"/>
Advantages
Full control about how the Model's properties are displayed on the View as shown in p2: int gets converted to bool on demand.
Changes of the properties of the ViewModel could be raised individual, might be a little performance increase compared to option 2.
Disadvantages
Violation of DRY.
More Code to write/maintain.
Modifications to the Model/ViewModel could easily become shotgun surgery.
Option 2 - Treat the whole Model as property of the ViewModel
Architecture
class Model
{
public string p1 { get; set; }
public int p2 { get; set; }
}
class ViewModel : INotifyPropertyChanged
{
// Model instance for this ViewModel (private field with public property)
private Model _M;
public Model M
{
get { return _M; }
set
{
_M = value;
// Raising the changing notification for the WHOLE Model-instance.
// This should cause ALL bound View-controls to update their values,
// even if only a single property actually got changed
RaisePropertyChanged("M");
}
}
// Initialize the Model of the ViewModel instance in its ctor
public ViewModel()
{ M = new Model(); }
}
Binding
<Textbox Text="{Binding M.p1}"/>
<Checkbox IsEnabled="False" IsChecked="{Binding M.p2, Mode=OneWay, Converter={StaticResource InverseBooleanConverter}"/>
Advantages
Can save a lot of code.
Reduces complexity.
Increases maintainability.
Disadvantages
In this approach, the ViewModel is nothing more than a continuous-flow water heater for the Models properties, except for some possible interaction logic for the View.
No control about how the Model's properties are displayed in the View - which ultimately leads to total needlessness of the ViewModel and implementation of conversion logic in the View.
It is the responsibility of your ViewModel to expose the Model to the View, you should not expose the Model's properties as additional properties in the ViewModel, instead, your View should bind directly to the model.
Additionally, it isn't wrong to have logic in your Model, in fact, it makes more sense to contain model related code within the model, as opposed to the ViewModel.
Here is an example:
public class Movie
{
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
//Notify property changed stuff (if required)
//This is obviously a stupid example, but the idea
//is to contain model related logic inside the model.
//It makes more sense inside the model.
MyFavourite = value == "My Movie";
}
}
private bool _MyFavourite;
public bool MyFavourite
{
get { return _MyFavourite; }
set
{
_MyFavourite = value;
//Notify property changed stuff.
}
}
}
So to answer your question a little more directly, you should expose your model in the view model as a property.
public class ViewModel
{
private Movie _Model;
public Movie Model
{
get { return _Model; }
set
{
_Model = value;
//Property changed stuff (if required)
}
}
...
}
Therefore, your View will bind to the Model property, like you have already done so.
EDIT
In the example for casting to the type, you can implement a read-only property in your Model, like so:
public bool MyBool
{
get
{
return MyInt > 10; }
}
}
Now the magic here would be that you will need to call the INotifyPropertyChanged for this property whenever MyInt changes. So your other property would look something like this:
public int MyInt
{
get { ... }
set
{
_MyInt = value;
//Notify property changed for the read-only property too.
OnPropertyChanged();
OnPropertyChanged("MyBool");
}
}
In my view, Model should not have RaisePropertyChanged stuff. Some view models (e.g. Blazor) might not need it, others (e.g. WPF) might use other mechanisms like DependencyProperty. Thus, to me Model is a POCO class. Hence, it becomes ViewModel responsibility to report changes to the data up to the View. Consequently, ViewModel is bound to wrap Model's properties (OA's option 1).
You might want to look at AutoMapper to centralize the mappings.
I have a class like this.
public class ViewModel
{
public PengChat3ClientSock Sock { get; internal set; }
public ObservableCollection<Room> Rooms { get; internal set; }
public ViewModel(PengChat3ClientSock sock)
{
Sock = sock;
Rooms = new ObservableCollection<Room>();
}
}
and MainWindow.xaml.cs
public ObservableCollection<ViewModel> viewModel { get; set; }
(Of course it is initialized.)
And here is a constructor.
comboBox.ItemsSource = viewModel;
But here, i do not want to use viewModel, only viewModel.Sock.
How can i do this?
There is no viewModel.Sock, as viewModel is a collection of objects of type ViewModel, which contain that property.
Depending on your goals there are different solutions:
You can still bind comboBox to viewModel, but in the item template of the comboBox you can access the Sock property
You can create new collection that will contain only Sock objects ... but then you may have to make sure it is synchronized with the collection of ViewModel objects
Usually you would go about this in a slightly different way:
Create View (UserControl with all the ui elements you need)
Set your 'ViewModel' as DataContext on the newly created View (userControl.DataContext = viewModel)
In the view, where you define your combobox, you can now refer directly to the 'Sock' property on your viewmodel via Bindings.
Code:
<ComboBox ItemsSource="{Binding Sock}"/>
If you just want to set the ItemsSource from code-behind, which is kind of ugly, you might simply add the items in Sock to comboBox.Items - Alternatively you may need to create a new 'Binding' object from code-behind, but this is even uglier:
var binding = new Binding("Sock")
{
Source = viewModel
};
comboBox.ItemsSource = binding;
Please note that I haven't tested the 'Binding in code-behind approach', it's really an anti pattern to do it like that, especially if you're working with MVVM.
Also, you 'Sock' property is a class, and not a collection, so you won't really be able to do this; did you perhaps mean the 'Rooms' property of the ViewModel?
You can only bind an ItemsSource to a type which implements IEnumerable. I have run into this before, and made a converter to convert any object to a list. It's a simple and reusable solution, which leaves the ViewModel logic separated from the view logic:
Converter:
public class ItemToCollectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new List<object> { value };
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML
<UserControl.Resources>
<local:ItemToCollectionConverter x:Key="ItemToCollectionConverter"/>
</UserControl.Resources>
...
<ComboBox ItemsSource="{Binding Sock, Converter={StaticResource ItemToCollectionConverter}}"/>