I'm working on a WPF project, and I'm trying to follow the MVVM pattern. In the past I've only used DataTemplates to display information, but now I want to put a button on my template that performs an action related to the item containing the clicked button. I'm trying to figure out the best way to bind my code to my button in a way that the code knows which button was clicked.
My WindowViewModel contains relay commands which are exposed through command properties, as well as an ObservableCollection of `Items'.
public class WindowViewModel
{
public ICommand ChangeItemCommand { get; private set; }
public ObservableCollection<Item> Items {get;private set;}
public WindowViewModel()
{
ChangeItemCommand = new RelayCommand(new Action<object>(this.ChangeItem));
Items = new ObservableCollection<Item>();
}
public void ChangeItem(object o)
{
string key = (string)o;
//do something to the item with provided key
}
}
My ItemViewModel contains an ItemKey property to identify the item.
public class ItemViewModel
{
public string ItemName { get; private set; }
public string ItemKey { get; private set; }
}
My list box DataTemplate looks something like this.
<DataTemplate DataType="local:ItemViewModel">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding ItemName}"/>
<Button Command="???" CommandParameter="{Binding ItemKey}"/>
</StackPanel>
</DataTemplate>
So I'm trying to figure out the best way to bind the button command to WindowViewModel.ChangeItemCommand of the WindowViewModel.
One option I'm considering would be to add a command property to the ItemViewModel which is set when instances are created by WindowViewModel
public class ItemViewModel
{
public string ItemName { get; private set; }
public string ItemKey { get; private set; }
public ICommand ChangeItemCommand{ get; private set; }
}
<Button Command="{Binding ChangeItemCommand}" CommandParameter="{Binding ItemKey}"/>
Alternatively I could bind directly to the WindowViewModel.ChangeItemCommand property by using a RelativeSource.
<Button
Command="{Binding Path=ChangeItemCommand,
RelativeSource={RelativeSource AncestorType={x:Type MyAppAWindow}}}"
CommandParameter="{Binding ItemKey}"/>
Note: I'm not entirely sure I did that right
So which would be recommended, or is there another better way?
Both options are fine but the real decision to make is that who should be the owner of this action, In MVVM view models should be designed first and then views should use them properly.
For e.g.
if this action is say RemoveItem then I would say it belongs to WindowViewModel (as this is about changing the collection which WindowViewModel exposes).
but say this action is RefreshItemData or ChangeItemDetails(e.g.ItemName or ItemStatus) then it belongs to ItemViewModel (as this ItemViewModel can be used in other windows supporting this action)
So, I would advice you to first design the ViewModels considering functionality, re-usability etc. and then use suitable binding feature (i.e. RelativeSource, ElementName, direct etc.).
The latter is the preferred one.
There are several ways and it boils down to preferences. There are a lot of MVVM Frameworks out there like MVVM-Light and Caliburn Micro that makes binding to commands way way easier compared to pure WPF CommandBindings.
Related
I have been coding for over 4 years now... and time has come that I need to understand how to use MVVM in a perfect way, in order to be able to accomplish more difficult coding tasks.
More specifically, I would like to know how to use a model object for multiple viewmodels and views.
Sample scenario
Let's have an app that has two views: the first with a list of ItemModel objects (that shows all the objects available) and another one that has a list that only contains the favorites ones.
The ItemModel has an IsFavorite boolean property to determine whether or not it is one.
Until today, I have always accomplished scenarios like this in this way:
ItemModel class (a model-viewmodel class at the same time):
public class ItemModel : BaseBind // This class implements INotifyPropertyChanged
{
public ItemModel()
{
// ...
}
private bool isFavorite;
public Boolean IsFavorite
{
get { return isFavorite; }
set { SetProperty(ref isFavorite, value); OnPropertyChanged(); }
}
// All the model's and viewmodel's properties and functions are stored here...
public void GenericFunc()
{
App.Current.AppViewModel.GenericAppFunc(this);
}
public void FavoriteFunc()
{
App.Current.AppViewModel.FavoriteAppFunc(this);
}
public ItemModel Clone()
{
// Cloning the item with "new" constructor...
}
}
AppViewModel class:
public class AppViewModel
{
public AppViewModel()
{
// Initializing...
}
public ObservableCollection<ItemModel> ItemsList { get; set; }
public ObservableCollection<ItemModel> FavoritesList { get; set; }
public void UpdateFavorites()
{
FavoritesList.Clear();
foreach (var it in ItemsList)
{
if (it.IsFavorite) FavoritesList.Add(it.Clone());
}
}
public void GenericAppFunc(ItemModel item)
{
// ...
}
public void FavoriteAppFunc(ItemModel item)
{
// ...
}
}
First view, with all items listed:
<ListView ItemsSource="{x:Bind ViewModel.ItemsList, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:ItemModel">
<StackPanel>
<TextBlock Text="{x:Bind ItemTitle, Mode=OneWay}"/>
<Button Content="Generic func" Click="{x:Bind GenericFunc}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Second view, with only favorites listed:
<ListView ItemsSource="{x:Bind ViewModel.FavoritesList, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:ItemModel">
<StackPanel>
<TextBlock Text="{x:Bind ItemTitle, Mode=OneWay}"/>
<Button Content="Favorite func" Click="{x:Bind FavoriteFunc}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is my way of doing it all... having two completely independent lists and cloning objects everytime the user navigates to the favorites view.
It works, it always did it... BUT this is definitively NOT a good pattern.
To summarize, this is what I'm doing till now...
...and this is what I would like to achieve:
In order to have lightweight models like this...
public class ItemModel
{
public ItemModel()
{
// ...
}
public Boolean IsFavorite { get; set; }
// Only the model's properties and functions are stored here...
}
...and use it for multiple viewmodels/views.
Best regards and thank you for your attention.
In UWP there is no filtering of data on top of a single data source. While in WPF it was possible to use the same ObservableCollection of items and filter only when binding your views using a CollectionViewSource, it seems like the only solution is having two different collections like you are doing.
There is indeed no Filter property in the CollectionViewSource class for UWP so [...] you will have to filter the source collection itself (or use two different collections like you are already doing).
Surely you can remove the need to call UpdateFavorites() by hooking to the PropertyChanged event of your items and move them from one collection to the other when their IsFavorite property is set, like this:
public AppViewModel()
{
// Subscribing to changes in collection of items
ItemsList .CollectionChanged += ItemList_CollectionChanged;
}
private void ItemList_CollectionChanged(object sender, EventArgs e) {
//Here subscribe/unsubscribe to PropertyChanged event of each item
//as they come and go in the collection
}
private void Item_PropertyChanged(object sender, EventArgs e) {
//Here check IsFavorite property and add/remove the item from the favorites list.
//You can even instantiate another type of viewmodel object specific to favorites items.
}
Regarding the MVVM pattern, I would advice to have a lightweight ItemModel model like you suggest and have a collection of them stored in your model. Then you could have two collections, one with ItemViewMode objects and one with FavoriteItemViewModel objects in your AppViewModel. You could also have a single ItemViewModel class with an IsFavorite property and use triggers and styles to have them display differently.
I don't see a reason to clone.
What would be the problem with:
foreach (var it in ItemsList)
{
//if (it.IsFavorite) FavoritesList.Add(it.Clone());
if (it.IsFavorite) FavoritesList.Add(it);
}
or, just set up the FavoritesList as a filtered version:
public List<Item> FavoritesList => ItemsList.Where(x => x.IsFavorit);
as long as you Notify at the right actions this should work.
I have a program that when you click the button, it creates a person with random attributes.
If the content of the label changes with every different object (person) created, how do you define that in true MVVM style? I can't have the viewmodel control the view, right? So i can't
label.Content = person.hair_Color;
public class Person()
get set hair_Color, get set shirt_color, yadda yadda
Because there can be either 1 or an infinite amount of people, how do i dynamically add the content of a label, if i don't know how many there will be?
In 'true MVVM style', you would have something like:
Views
MainView containing:
A button "Add Person" <Button Command={Binding AddPerson}/>
A list containing some "PersonView" <ListBox ItemsSource="{Binding Persons}"/>
PersonView containing:
A label "Shirt" <TextBlock Text="{Binding Shirt}"/>
A label "Hair" <TextBlock Text="{Binding Hair}"/>
A rectangle (for the example) "ShirtGraphic" <Rectangle Background="{Binding Shirt, Converter={stringToColorConverter}/>
A rectangle "HairGraphic" <Rectangle Background="{Binding Hair, Converter={stringToColorConverter}/>
StringToColorConverter class, returning a color from a string
ViewModels
MainViewModel containing:
An observable collection property of PersonViewModel "Persons" public ObservableCollection<PersonViewModel> Persons { get; set; }
A command "AddPerson" public Command AddPerson { get; set; }
PersonViewModel containing:
A string property "Shirt" public string Shirt { get; set; }
A string property "Hair" public string Hair { get; set; }
This is pretty much just a mockup of what you would actually have, since implementation depends on the framework used, but the idea is here. You bind, you convert, etc.
It doesn't implement any INotifyPropertyChanged or ICommand
No DataTemplate is set for the ListBox (to actually display some PersonView)
Given the following classes:
public class Neighborhood
{
public IEnumerable<House> Houses { get; set; }
}
public class House
{
public Address Address { get; set; }
public IEnumerable<Room> Rooms { get; set; }
}
public class Room
{
public IEnumerable<Furniture> Furniture { get; set; }
}
I want views that look something like this:
<!-- Want DataContext to be a NeighborhoodViewModel, not a Neighborhood -->
<NeighborhoodView>
<ListBox ItemsSource={Binding Houses}/>
<Button Content="Add House"/>
<Button Content="Remove House"/>
</NeighborhoodView>
<!-- Want DataContext to be a HouseViewModel, not a House-->
<HouseView>
<TextBox Text={Binding Address}/>
<ListBox ItemsSource={Binding Rooms}/>
<Button Content="Add Room"/>
<Button Content="Remove Room"/>
</HouseView>
<!-- Want DataContext to be a RoomViewModel, not a Room -->
<RoomView>
<ListBox ItemsSource={Binding Furniture}/>
<Button Content="Add Furniture"/>
<Button Content="Remove Furniture"/>
</RoomView>
However, I don't want NeighborhoodViewModel to contain HouseViewModels. Rather, it should be like:
public class NeighborhoodViewModel
{
public IEnumerable<Room> Rooms { get; }
public ICommand AddHouseCommand { get; }
public ICommand RemoveHouseCommand { get; }
}
How can I declare bindings to models in XAML, but have the bindings be transformed into viewmodels?
There are two general ways you can create this type of effect. The first way is to create a static view and put a DataContext behind each view. This is not MVVM where the views are generated by the ViewModel but this will get your bindings to work. This is an example of view.
<Grid>
<HouseView x:Name="myHouse">
</HouseView>
</Grid>
In the code behind you can get access to your HouseView and set the data context
public MainWindow()
{
myHouse.DataContext = new MyHouseViewModel();
}
This will get your bindings to work for each one of these controls.
I will say that this is not the best practice of WPF and it is certainly not a way to develop large projects. However this will do for quick and dirty coding in my opinion. You can find out more on how to do proper MVVM style of coding here.
I am developing windows phone app .In app,I want to put multiple check box.I able to put multiple check box.But when i checked on check box i want getting its content(check box content).For that i am use checked event and also click event but i cant get result as i want.My xaml code is as below:
<ListBox Name="hobbylist" ItemsSource="{Binding}" Margin="0,0,10,10" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Name="hobbycheck" Content="{Binding Path=Title}"
IsChecked="{Binding IsSelected}" ClickMode="Release"
Click="CheckBox_Click" ></CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Please help me ...
I think you are using the Checkbox not correctly according to its purpose.
Checkbox should represent a state (e.g. yes/no) regarding a subject. Still, you just have to use the Checked event when the checkbox gets checked and Unchecked otherwise.
So in the Checked event, get the content you wish.
Edit
You have to maintain this with the MVVM pattern somehow. For that, there are plenty of examples in the internet, I am sure you can handle that.
Instead of having Click="CheckBox_Click", use the Check event :
private void CheckBox_Checked (Object sender, EventArgs e)
{
var currentCheckBoxItem = sender as CheckBox;
if (currentCheckBoxItem.IsChecked == true)
{
//you manipulation here...
}
}
Still. this might just not work, because you haven't provided enough details of your matter.
Edit 2 A little of MVVM...
First, make a Hobby model class, with a single string property (you might change your mind later to add more properties, Idk) :
public class Hobby : INotifyPropertyChanged
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged();
}
}
private bool _isSelected;
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
OnPropertyChanged();
}
}
//You can add some multiple properties here (***)
public Hobby (string hobbyName, bool isSelected)
{
Name = hobbyName;
IsSelected = isSelected;
}
//INotifiyPropertyChanged interface member implementation ...
}
(* ) For example, a short description and then bind it on the View. The major advantage of this MVVM pattern is logic separation, so if something has to change, the separation of each component makes it easier.
Second, create a ViewModel class (you should implement INotifyPropertyChanged interface) :
public class HobbiesViewModel : INotifyPropertyChanged
{
private ObservableCollection<Hobby> _hobbies;
public ObservableCollection<Hobby> HobbiesCollection
{
get
{
return _hobbies;
}
set
{
_hobbies = value;
OnPropertyChanged();
}
}
//Constructor
public HobbiesViewModel
{
HobbiesCollection = new ObservableCollection<Hobby>();
}
//INotifyPropertyChanged interface member implementation ...
}
Third, create an instance of the ViewModel (the ObservableCollection). Use this quick help out : In the App.xaml.cs, create a static object and use it through the app as you need it :
public partial class App
{
//This already exists in your app's code, but I've written it to
//make an idea where to write the Hobbies object
public static PhoneApplicationFrame RootFrame { get; private set; }
public static HobbiesViewModel Hobbies;
//Again, the already existing constructor
public App()
{
...
Hobbies = new HobbiesViewModel();
}
Now, you almost have it all set; You have the Model, you have the ViewModel, all that's left is to create the connection with the View. This can be easily done through binding. The ViewModel represents the DataContext of your control (in your case the LongListSelector, so in that View's (Page's) constructor, write the following statement :
yourListControlName.DataContext = App.Hobbies;
Now the binding is the only thing left. This is done in XAML code. I won't put a whole chunk of XAML code here, cause you know best how your control looks like. Still, judging by the short sample you provided, there a few adjustments only :
The items source of the list XAML control will be bound to the ObservableCollection object name of the ViewModel representing the control's DataContext. A bit fuzzy, huh? To be clearer, in this case, you need to write ItemsSource="{Binding HobbiesCollection}", the ObservableCollection. Also, in the template, you should have that CheckBox which is bound on your Model's properties :
<DataTemplate>
<StackPanel Orientation="Horizontal"> //StackPanel is kinda useless if you have
//only one child control in it. But I wrote
//according to your code.
<Checkbox Content="{Binding Name}" IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
</StackPanel>
</DataTemplate>
Now, here things are a bit unclear to me. Why would you use the Checkbox? I've thought of the next possible scenario : You come with some string of your hobbies through deserialization of the Json Data. To add them to the ViewModel, you need only :
App.Hobbies.HobbiesCollection.Add(new Hobby("firstHobbyFromJson", true));
App.Hobbies.HobbiesCollection.Add(new Hobby("secondHobbyFromJson", true));
This would make all hobbies already selected in the View. I guess, you would add some other hobbies, the user doesn't have which are not selected and could add them now :
App.Hobbies.HobbiesCollection.Add(new Hobby("aNewHobby", false));
App.Hobbies.HobbiesCollection.Add(new Hobby("anotherNewHobby", false));
At this point, the user has all its previous hobbies in the list and as well some new hobbies you provided him. After his selection is done, if you need to serialize the Json with only the selected hobbies, you could get like this :
var userHobbies = App.Hobbies.HobbiesCollection.Where(h => h.IsSelected);
or with a foreach and get only those hobby objects which have the IsSelected property as true.
Good luck!
I found a Simpler solution.
My Model
You need to use two variables otherwise you may get an 'stackoverflowexception'
public class ModelObj
{
public int position { set; get; }
public bool isChecked
{
get { return IsChecked; }
set { IsChecked = value; }
}
public bool IsChecked;
}
Code to be added in xaml:
isChecked in xaml sets the ListView Checkbox
Mode=TwoWay updates the isChecked boolean value of the model class
<CheckBox IsChecked="{Binding isChecked , Mode=TwoWay}" Checked="checkBox_Checked" >
c# Code that handles the event
private void checkBox_Checked(object sender, RoutedEventArgs e)
{
foreach (ModelObj obj in listItem)
{
if (obj.isChecked == true)
{
int selectedPosition = obj.position;
}
}
}
In WPF, I'm trying to bind multiple controls, but the second control isn't changing when the first control is changed.
I have two classes: a Task class, and a Log class, which is stored as a collection in the Task class. The list boxes below are bound to the Tasks, and the inner Logs for the selected Task.
The problem is that the list boxes are populated fine at first load, but if I select a different task, I'd expect the Logs to be update to the collection for the new Task, but it doesn't change from those from the originally selected task on first load. What am I missing?
In the designer:
<ListBox x:Name="listBoxTasks" ItemsSource="{Binding}" DisplayMemberPath="Key"
Grid.Row="0" Grid.Column="0" Grid.RowSpan="2">
</ListBox>
<ListBox x:Name="listBoxLogs"
ItemsSource="{Binding Logs}" DisplayMemberPath="EntryDate"
Grid.Row="1" Grid.Column="1">
</ListBox>
In the code behind:
public MainWindow()
{
InitializeComponent();
IMongoCollection<Task> tasks = DataManager.GetData();
this.DataContext = tasks.AsQueryable();
}
The Task class:
public class Task : BusinessBase<Task>
{
public ObjectId _Id { get; set; }
public string Key { get; set; }
public string Description { get; set; }
public string Summary { get; set; }
public string Details { get; set; }
public IEnumerable<Log> Logs { get; set; }
public IEnumerable<Link> Links { get; set; }
public IEnumerable<String> RelatedKeys { get; set; }
public IEnumerable<TaskItem> Items { get; set; }
}
Your Task class need to implement INotifyPropertyChanged interface so that as soon as there is any change in the underlying data it can tell WPF UI that something has changed now update/refresh your controls agains
Your task class need to implement INotifyPropertyChanged
http://msdn.microsoft.com/en-us/library/ms743695.aspx
You have to bind your first ListBox SelectedItem to object of Task model and add event handler for SelectionChanged. inside the this event you have to populate your logs by selected Task model also you have to implement INotifyPropertyChanged in your class.
It looks to me like the second binding should not work at all, as the DataContext is an enumerable of Tasks and the enumerable itself has no property called Logs. You could try working with IsSynchronizedWithCurrentItem and a binding to the current item:
<ListBox x:Name="listBoxTasks" ItemsSource="{Binding}" DisplayMemberPath="Key"
Grid.Row="0" Grid.Column="0" Grid.RowSpan="2"
IsSynchronizedWithCurrentItem="True"> <!-- Set this -->
</ListBox>
<ListBox x:Name="listBoxLogs" DisplayMemberPath="EntryDate"
Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding /Logs}"> <!-- Note the slash which indicates a binding to the current item -->
</ListBox>
You could also bind to the SelectedItem of the other ListBox but this introduces a redundant dependency between the controls. Also note that if you change any property in your data-objects you need to implement the interface mentioned by the other answerers, INotifyPropertyChanged.
I have it all working now. I implemented INotifyPropertyChanged, although that didn't solve the problem.
I am now using the MVVM pattern. This helped...the NoRM library I was using didn't have a SelectionChanged event. I created a View Model and was able to convert those Models to ObservableCollections. Now I'm just setting the Logs control DataContext on selection changed for the Task class.