Change to a different DetailsTemplate on a MasterDetailsView when selection changes - c#

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; }
}

Related

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

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)

WPF - Binding class with Lists of strings to a ListBox

I have a problem with binding multiple Lists to a ListBox. I want that every List has a different DataTemplate with a different color.
I have following model classes
public class Users
{
public Members Members{ get; set; }
}
public class Members
{
public List<string> Moderators { get; set; }
public List<string> Viewers { get; set; }
}
I have following ViewModel with INotifyPropertyChanged
private Users users;
public Users Users
{
get { return users; }
set
{
users= value;
RaisePropertyChanged("Users");
}
}
And I'm binding to this ListBox
<ListBox ItemsSource="{Binding Users.Members.Viewers}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now I only have that one List bound to the ListBox. It works great but I want the other list also bound to the same ListBox. Besides that I want that Moderators have a different template.
I tried many different things but nothing seemed to work.
Instead of removing the names from the origination object why not keep it and specify different colors based off of the originating class?
Besides that I want that Moderators have a different template.
If you only have strings that is impossible. Remember the listbox ultimately sees only one list; so in one list, how is it possible to tag a string as either moderator or viewer?
a different DataTemplate with a different color.
If there are only strings I suggest you create wrapper classes, one for moderators and one for viewers, then project the strings into those classes to be held. Then you can follow my suggestion/example below.
Via the use of the Composite collection to hold different items (or one could actually use a base class list or a interface list if the instances have that commonality) and then have specialized data templates which look for the originating class, it can be done.
Example
I have two classes one named Ships and one named Passage. Note that both classes both have a Name property, but one could use something other than Name for either or both in the templates.
Below I define the data templates and my listbox.
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type c:Ship}">
<TextBlock Text="{Binding Path=Name}"
Foreground="Red" />
</DataTemplate>
<DataTemplate DataType="{x:Type c:Passage}">
<TextBlock Text="{Binding Path=Name}"
Foreground="Blue" />
</DataTemplate>
</Grid.Resources>
<ListBox Name="myListBox"
Height="300"
Width="200"
ItemsSource="{Binding MyCompositeCollection}">
</ListBox>
</Grid>
So what will happen is that my ships will be red and the passages will be blue.
Here is my code in the VM:
private CompositeCollection _MyCompositeCollection;
public CompositeCollection MyCompositeCollection
{
get { return _MyCompositeCollection; }
set { _MyCompositeCollection = value; OnPropertyChanged("MyCompositeCollection"); }
}
Here I load the composite collection:
var temp = new CompositeCollection();
Ships.ForEach(sh => temp.Add(sh));
Passages.ForEach(ps => temp.Add(ps));
MyCompositeCollection = temp;
In order to combine two Lists and set it to ItemsSource use CompositeCollection.
WPF can set distinct template by using ItemTemplateSelector but it entails class to be diffrent in some way. Your type is string so it does not differ in any way. My hint is to create enum as follows
enum MemberType
{
Moderator,
Viewer
}
and following class:
class Person
{
public string Name{get;set;}
public MemberType Type{get;set;}
}
then change to this
public class Members
{
public List<Person> Moderators { get; set; }
public List<Person> Viewers { get; set; }
}
and eventually in ItemTemplateSelector
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate ViewerDataTemplate;
public DataTemplate ModeratorDataTemplate;
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var member = item as Person;
switch (member.Type)
{
case MemberType.Moderator:
return ModeratorDataTemplate;
case MemberType.Viewer:
return ViewerDataTemplate;
}
return null;
}
}

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; }
}

How to populate a control inside a DataTemplate whenever the template is instantiated?

I have an ItemsControl with a DataTemplate describing how to display each item. The UI I'd like to have inside the DataTemplate is a bad fit to be modelled by XAML and I am going to have to populate a Grid in code.
How do I get this code to run every time my DataTemplate is instantiated, so that I get a chance to populate the bits I couldn't express in XAML?
To expand a little, consider a simplified example. The VM looks like this:
class MyItem
{
public string Name { get; set; }
public MyGrid Grid { get; set; } // describes a complex grid-like model
}
The DataTemplate looks like this:
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
<Grid/>
</DataTemplate>
The <Grid/> is the thing I want to populate in code based on MyItem.Grid. How can I do this?
(If you are going to say that I shouldn't populate the <Grid/> in code but just use XAML, please answer this question instead)
You can easily hook the Loaded event on the Grid.
<Grid Loaded="Grid_Loaded">
private void Grid_Loaded(object sender, ....)
{
var grid = (Grid)sender;
var item = (MyItem)grid.DataContext;
//Go time
}

Problem with DataBinding and style [WP7]

I have been trying to bind listbox with an observableConnection in Xaml on WP7 with no luck. All I want to do is to make listbox to show an instance of my class that inherits from ObservableConnection and apply some style on listbox. I can do this from code like
public Storage.Categories tmp;
...
tmp = new Storage.Categories();
listBox1.ItemsSource = tmp;
but how to apply style on that?
Here is code:
<ListBox Height="497"
HorizontalAlignment="Left"
Margin="0,104,0,0"
Name="listBox1"
VerticalAlignment="Top"
Width="450">
namespace Genesa.Storage
{
public class Categories : ObservableCollection<Category>
{
public void LoadCategories()
{
// deserialize obiect
}
public void SaveCategories()
{
// serialize obiect
}
public Categories() : base()
{
LoadCategories();
}
}
public class Category
{
public Category() { }
public String name { get; set; }
public String description { get; set; }
public Category(String _name, String _description)
{
name = _name;
description = _description;
}
public override string ToString()
{
return String.Format("{0} - {1}", name, description);
}
}
}
You're going to want to use a DataTemplate. A data template let's you structure the items in your ListBox. For example:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding name}" />
<TextBlock Text="{Binding description}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Also, you might want to reconsider inheriting from ObservableCollection. If what you're doing is as simple as it looks above, you probably want to stick to creating a class which contains an ObservableCollection and which implements the INotifiyPropertyChanged interface. This is assuming you're using the MVVM design pattern. If you're not, feel free to disregard this suggestion. If you are implementing MVVM, you also want to make the Category class implement the INotifyPropertyChanged interface.
As Jared suggests, the most appropriate approach to your solution is to provide an ItemTemplate for the ListBox that defines the structure of each item in the ListBox, which enables you to bind directly to properties on your class, instead of having to override the ToString method. However, there is a small mistake in Jared's DataTemplate because it can only contain a single item, so you need to wrap the elements in some kind of container, as shown below:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding name}" />
<TextBlock Text="{Binding description}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You only need to implement the INotifyPropertyChanged on your Category class if the properties can change during the lifetime of that object. If the values are constant throughout it's lifetime, then there's no need.
usually the ObservableCollection is member of the ViewModel to which the View binds to. You don't have to inherit from ObservableCollection and the logic from Categories class can be placed inside ViewModel.
Then you need to set DataContext of Page or other object in hierarchy to be the ViewModel and then you can bind for example ListBox.ItemsSource to ViewModel.ObservableCollection.
After that DataTemplate will work in scope of Category (single item in ObservableCollection).
Regarding the logic of loading etc, there is usually one more layer responsible for these operations, which is injected to ViewModel, but if you don't want it, it's just fine.

Categories