WPF how to change label of property from dynamic object in MVVM - c#

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)

Related

How properly use binding in wpf?

Let say I got app that is going to read data from database.
So in c# i got class like this:
public class Employee {
public string Name { get; set; }
public string Title { get; set; }
}
And my wpf window looks like:
<TextBlock Margin = "10" Width = "100" Text = "{Binding Name}" />
As I understand, to display in my window name of the employee I should use DataContext to bind object Employee with my wpf:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = new Employee(...);
}
Ok, but what if i have another textbox to display object from another class?
public class Movie {
public string MovieTitle { get; set; }
}
<TextBlock Margin = "10" Width = "100" Text = "{Binding MovieTitle}" />
Where should I use DataContext to bind this all, that two textbox are display on my screen, and both display properties from another object?
It's not a must, but in WPF it is recommended to follow MVVM design patterns.
This means you need a View (as you created), a Model and a ViewModel.
The View should have a DataContext point to a ViewModel, and the binding in the View's XAML should be pointing to the ViewModel's properties.
So in your case, you can create a "main" ViewModel as your main DataContext object, and this ViewModel can have a string property called "EmployeeName" (or Employee ViewModel object with property "Name") and then the property can be assigned, or fetch the information directly from the Model.
Do not forget to fire a property change notification from the setter of any view model property, e.g. the PropertyChanged event of the INotifyPropertyChanged interface.
public string EmployeeName
{
get { return _employeeModel.Name; }
set
{
_employeeModel.Name = value;
PropertyChanged?.Invoke(
this, new PropertyChangedEventArgs(nameof(EmployeeName)));
}
}
If you're following MVVM properly:
Your ViewModel must not know the View, it can only know Models or other ViewModels.
Your Model must not know the View or the ViewModel, it can only know other Models.
As others were trying to point out, add another thing to the main class. Or, as Yair mentioned to create a view model. Try to think of it this way.
public class MainWindowViewModel
{
public MainWindowViewModel()
{
OneEmployee = new Employee();
OneMovie = new Movie();
}
public Employee OneEmployee {get; set;}
public Movie OneMovie {get; set;}
// any other things you want to expose, button relay commands labels, etc...
public string SomeOtherThing {get; set;}
}
Now, in your MainWindow (or whatever other secondary window you develop), in its constructor, just set
DataContext = new MainWindowViewModel();
Now, back to your XAML Your xaml's data context is to the overall view model that has BOTH objects, a label, and whatever else you want. When binding and its all in the same level in the xaml, you can do binding to OneEmployee.Name, or OneMovie.Title, or whatever other parts going on.
However, let say you are sectioning off the display and have one area designated for the employee information and another based on a movie. You can set a data context to that sub-component to the object in question, so the inner controls are now defaulted to that. Removes the need of "object.property" notation within. Ex:
<UserControl DataContext="{Binding OneEmployee}">
<Grid>
<define your grid rows/columns for example>
<Label Content="Name" Grid.Row="0" Grid.Column="0" />
<TextBox Width="100" Grid.Row="0" Grid.Column="1"
Text="{Binding Name}" />
</Grid>
</UserControl>
Notice above the entire "UserControl" has its default data context to just the OneEmployee object. So everything within can refer to properties exposed directly on that object. In this example, the entire context of the user control is to show information about the one employee and not the movie. Same can be done for a single movie.
I actually like doing it this way and create individual user controls with inner grid type content to span its own rows/columns. Then, when I put them into a main view area, I dont have to worry about messing with other nested controls, grids. The single user-control is all self-contained with all its alignments, borders, etc. If I need to adjust employee info layout, I just change that one class, not the outer view that the employee is displayed within.

Change to a different DetailsTemplate on a MasterDetailsView when selection changes

In the MasterDetailView I have a list of different items. When the item is selected the appropriate DetailsTemplate must be loaded.
I have absolutely no idea how to do this!
This example is what I expect the interaction to be.
The list on the left side of the MasterDetailView is filled with pigs, chickens and gold fish.
When the user selects the pig item, then the details of the pig must appear in the details section of the MasterDetailView control.
When the user selects a Gold Fish item then the Gold Fish's details must appear.
etc.
If your left menu listview's items are instances of IAnimal.
Just bind SelectedItem to a property SelectedAnimal on your view model.
Create a grid and place a ContentControl to host the detail view in that grid. Bind its Content to SelectedAnimal and in the Grid's Resources add a datatemplate for each animal type and map it to a View that would visualize the details you want to show for the IAnimal.
So when you click an IAnimal in the left menu it will be set to the VM SelectedAnimal property. The ContentControl will then show that IAnimal using the View defined by the DataTemplate.
I would use the same approach to define how the left menu should look for the IAnimal items in the menu list as well.
Add to my above comment. If you meant that you're a beginner in UWP. You just want to know how to start to use the MasterDetailsView XAML Control of the windows community toolkit. Then, the official document Windows Community Toolkit Documentation would be a good start.
And the windows community toolkit also has a complete code sample for each controls. The samples are on github. For the MasterDetalsView is here.
I even made a simple code sample for your reference according to your description.
<controls:MasterDetailsView
ItemsSource="{Binding animals}">
<controls:MasterDetailsView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"></TextBlock>
</DataTemplate>
</controls:MasterDetailsView.ItemTemplate>
<controls:MasterDetailsView.DetailsTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding DisplayName}"></TextBlock>
<TextBlock Text="{Binding Description}"></TextBlock>
</StackPanel>
</DataTemplate>
</controls:MasterDetailsView.DetailsTemplate>
<controls:MasterDetailsView.NoSelectionContentTemplate>
<DataTemplate>
<TextBlock Text="No Selection"></TextBlock>
</DataTemplate>
</controls:MasterDetailsView.NoSelectionContentTemplate>
</controls:MasterDetailsView>
public sealed partial class MainPage : Page
{
public ObservableCollection<Animal> animals { get; set; }
public MainPage()
{
this.InitializeComponent();
animals = new ObservableCollection<Animal>();
animals.Add(new Animal() { DisplayName = "Pig", Description = "This is a pig" });
animals.Add(new Animal() { DisplayName = "Dog", Description = "This is a dog" });
this.DataContext = this;
}
}
public class Animal
{
public string DisplayName { get; set; }
public string Description { get; set; }
}

WPF command bindings to a DataItemTemplate with MVVM

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.

WinRT XAML data binding a List of objects that contain a List

I have a List of objects that contain another List. I want to bind both Lists to different controls (one nested within the other - a ListView as GridViewItem). But I can't get the xaml to work.
Very close to this question comes Binding List of Lists in XAML?.
And there is an article regarding this in the MSDN documentation:
How to bind to hierarchical data and create a master/details view - could be the solution, but I find it difficult to apply it to my code.
Other articles touch this topic, but not that good and also as a new user I'm not allowed to include more than two hyperlinks in a question.
My code looks similar to this (changed to city / restaurant scenario for clarity):
Model:
public class City
{
string Name { get; set; }
List<Restaurant> RestaurantList { get; set; }
//.. also a constructor with parameters for the properties and an overriding toString method that returns Name
}
public class Restaurant
{
string Name { get; set; }
List<Uri> UriList { get; set; }
//.. also a constructor with parameters for the properties and an overriding toString method that returns Name
}
Code-behind (LoadState method):
//.. getting a List of cities (with restaurants), that is being created in some model class
this.DefaultViewModel["Items"] = Cities;
Some people set the DataContext instead. I got this from the MSDN tutorials and it worked so far. But I'm not sure which is "better".
Okay now the XAML:
I want to have a GridView with the Cities as GridViewItems. Within one GridViewItem there's a Grid, displaying the City's Name in the top row and a ListView below. The ListView contains the Restaurants (only of that City!). The ListViewItems are only TextBlocks showing the Restaurant's Name.
I want only the Restaurants to be clickable.
Like this:
<!-- the following line is at the very top and the reason why it should work without setting DataContext explicitly -->
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
<!-- ... -->
<GridView Grid.Row="1" ItemsSource="{Binding Items}" SelectionMode="None">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Height="500" Width="200" Margin="50" Background="Gray">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="5*"/>
</Grid.RowDefinitions>
<TextBlock TextWrapping="Wrap" Text="{Binding Name}"/>
<ListView
Grid.Row="1"
ItemsSource="{Binding RestaurantList}" IsItemClickEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Tapped="Restaurant_Click"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
This way only gray boxes are shown. When changing the TextBlock's binding to Text="{Binding}" then at least the Names of the Cities are shown. Which I don't understand and also don't want, because I guess the overriding of the toString methods is not meant to be used this way. The Names of the Restaurants don't show up in both cases.
Also, the scrolling somehow broke in this view, but that's a different story I suppose.
So: What's wrong with the data binding in the XAML?
The databinding engine needs public properties (the link is about WPF but the same concepts apply in WinRT):
You can bind to public properties, sub-properties, as well as
indexers, of any common language runtime (CLR) object.
But if you don't specify it explicit the compiler treats members by default "the most restricted access you could declare for that member" e.g. private in your case.
So you need to declare your properties as public:
public class City
{
public string Name { get; set; }
public List<Restaurant> RestaurantList { get; set; }
}
public class Restaurant
{
public string Name { get; set; }
public List<Uri> UriList { get; set; }
}

WPF Data Binding with multiple controls

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.

Categories