showing different user controls - WPF MVVM - wpf

I created a dbml file, which automatically created the designer.cs
In the designer.cs (which is the Model in the MVVM) there are two different classes in the database:
ElementA and ElementB.
I have two user controls:
Element_A_UserControl - displays a single instance of ElementA
Element_B_UserControl - displays a single instance of ElementB
There is another user control that has two stack panels.
The first stack panel displays a list of Element_A_UserControl
The second stack panel displays a list of Element_B_UserControl
Here is the stack panel #1 XAML:
<StackPanel>
<ItemsControl ItemsSource="{Binding AllElements_A}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<vw:Element_A_UserControl x:Name="elementA">
</vw:Element_A_UserControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Here is the stack panel #2 XAML:
<StackPanel>
<ItemsControl ItemsSource="{Binding AllElements_B}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<vw:Element_B_UserControl x:Name="elementB">
</vw:Element_B_UserControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Up to now everything works fine.
I want to have one stack panel, which displays a list of ElementA or a list of ElementB depending of a condition.
Note: The property to get list of elements is different.
i.e.
ItemsSource="{Binding AllElements_A}
ItemsSource="{Binding AllElements_B}
I hope that my question is clear enough.
Dazy.

One way is that you could try to use a conditional DataTemplate. Something like this:
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:ElementAType}">
<vw:Element_A_UserControl x:Name="elementA">
</vw:Element_A_UserControl>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ElementBType}">
<vw:Element_B_UserControl x:Name="elementB">
</vw:Element_B_UserControl>
</DataTemplate>
</ItemsControl.Resources>
Then, in your viewmodel, create:
public ObservableCollection<object> CombinedCollection {get; set;}
and load it with either of your collections conditionally.
Alternatively, keep both ItemsControls in your XAML, and conditionally hide/show them using Visibility and a BooleanToVisibilityConverter. Given these two choices, I would likely choose this one, as it is clearer in the code, and easier to maintain than the conditional DataTemplate above. However, you seemed to indicate that you didn't want to do that, so I presented the first one as an option.

Related

WPF: Can I have multiple ItemsControls with different ItemsSources?

In my WPF App, I want to have several items that are generated by the user on runtime. These are from different classes and hence, my original idea was to add them to different Observable Collections and then use them as ItemsSources od different ItemsControls. However, WPF gives me the error System.InvalidOperationException: Items collection must be empty before using ItemsSource. I'm not a WPF expert, but the answer to THIS SO question seems to indicate that I can only have 1 ItemsControl.
THIS SO question indicates that maybe I should use the CompositeCollection Class, but unlike in the cited question, I have several completely different Observable Collections for completely different tasks.
Here is the relevant part of my XAML.CS with two Collections: 1 of a custom Interface type and 1 of custom Class type
public MainWindow()
{
InitializeComponent();
DefaultWindowDefinition.ItemsSource = ProcessElements = new ObservableCollection<IProcessSimulator>();
PathControl.ItemsSource = PathElements = new ObservableCollection<VisualPath>();
}
And here is the relevant part of the XAML I tried to use:
<Grid x:Name="MainGrid"
Background="{StaticResource Alternating}"
MouseLeftButtonUp="grid_MouseLeftButtonUp"
ShowGridLines="True">
<ItemsControl Name="DefaultWindowDefinition"
ItemsSource="{Binding ProcessElements}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--HERE IS A LONG LIST OF ELEMENTS-->
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!--TEMPLATE FOR THE 1ST ITEMSCONTROL-->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<!--STYLE PROPERTIES FOR THE 1ST ITEMSCONTROL-->
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
<ItemsControl Name="PathControl"
ItemsSource="{Binding PathElements}">
<DataTemplate>
<!--HERE IS A LIST OF OTHER TYPE OFELEMENTS-->
</DataTemplate>
</ItemsControl>
</Grid>
How should I approach this problem or rather, what C#/WPF element should I use? A reference and some words of simple explanation is more than enough, I can google the rest myself, I just don't know, what to look for really.
You should set the ItemTemplate property of the "PathControl" to your DataTemplate:
<ItemsControl Name="PathControl" ItemsSource="{Binding PathElements}">
<ItemsControl.ItemTemplate> <!-- NOTE THIS -->
<DataTemplate>
<!--HERE IS A LIST OF OTHER TYPE OFELEMENTS-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you omit the <ItemsControl.ItemTemplate> element, you are adding a DataTemplate to the Items collection of the ItemsControl and you can't also set its ItemsSource property.
Trying to do so will results in an System.InvalidOperationException exception being thrown with the error message you are getting.
It is perfectly fine for several ItemsControl to bind to the same source collection.
It seems like you are setting the ItemsSource twice. Once in the code behind and once in the XAML. Remove the code behind that sets the Items source and just initialize the observable collections. The XAML should take care of binding to the collections.

Should I use DataTemplates or a UserControl to show different controls for extended classes?

I have a User class and an Author class that extends User. I have an ObservableCollection<User> being displayed in a ListBox. For this, I have a DataTemplate to display each item and another to display each selected item. I also have a column of TextBoxes that are bound to the properties of the ListBox.SelectedItem property. So far, so good.
At the moment, I am displaying extra controls in the column and DataTemplates if the selected User is an Author and it all works fine, but I'm cheating. I have added an IsAuthor bool property into the User class so that I could bind to it and determine whether a User was an Author. I know it's wrong, but I couldn't work out any other way to do it, so my first question is how do you display extended classes differently from the base class? I tried a different DataTemplate for the type Author, but it never worked... maybe because the collection was of type User?
The second question is should I have all of the many TextBox controls in the column in a UserControl and change the Visibility of the Author related controls, or somehow put them in a DataTemplate and create one for each type? I am using the first method currently and the problem is that each control bound to an Author property is throwing errors (I can see them in the Output window in Visual Studio) when the currently selected item is not an Author.
I have a similar setup which uses data templates and it works just fine with inherited classes. This is how I did it.
<ListBox Name="UserList" ItemsSource="{Binding Path=Users}"
ItemTemplate="{StaticResource ShowUserName}"
SelectedItem="{Binding Path=SelectedUser, Mode=TwoWay}">
</ListBox>
<ContentControl Content="{Binding ElementName=UserList, Path=SelectedItem}"/>
In the Window.Resources section I have the following DataTemplates:
<DataTemplate x:Key="ShowTime" DataType="TestApp.User">
<TextBlock Text="{Binding Path=Name}" HorizontalAlignment="Center"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:User}">
<StackPanel Margin="10">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text="{Binding Path=Age}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Author}">
<StackPanel Margin="10">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text="{Binding Path=Age}"/>
<TextBlock Text="{Binding Path=FirstTitle}"/>
</StackPanel>
</DataTemplate>
The first template is what will be displayed in the list itself. We are referencing it by key in the ItemTemplate property of the listbox. The other two data templates are used by the content control when determining what to display for the selected item. When the selected item is just a User, the User DataTemplate will be displayed, if an author is selected, the author DataTemplate will be shown.
The x:Type local:Author is referring to the the class type. local should be declared in your namespace declarations.
xmlns:local="clr-namespace:TestApp"
Keep in mind that this is my namespace, you will have to specify the one you are using. And of course the data templates are just basic examples, presumably you will want to do something more tailored to your application.
However it might be irritating to have to define two separate Data templates that are almost exactly the same for your two classes. Although you certainly could. I do it in my own application (not in this example), because what I want to display for each type are vastly different.
So what might be useful is to create a common DataTemplate for all the User properties, and simply extend this DataTemplate for Authors. If you want to do that you could set up your templates this way:
<DataTemplate x:Key="UserTemplate">
<!-- show all the properties of the user class here -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:User}">
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource UserTemplate}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Author}">
<StackPanel>
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource UserTemplate}"/>
<!-- show all the additional Author properties here -->
</StackPanel>
</DataTemplate>
So as you can see, both of the DataTemplates for User and for Author start out using the DataTemplate called "UserTemplate". But in the Author DataTemplate we will add Author specific properties.
I hope that helps.

Use a user control as a DataTemplate within a WPF application

I am trying to create a user control within a WPF application that will serve as a DataTemplate for a ListBoxItem. The user control a grid with 4 TextBlocks. This control also contains other shapes and images more for visual aid than anything so I am omitting them from the code in this question for clarity.
When I drop the user control on my mainwindow.xaml I can see the control and the properly bound fields point to the first record in the datasource. What I want to do is have this control render repeatedly within either a listbox or a wrap panel for each record within the database.
Can anyone provide me with a pointer or sample of how to have a user control render as a DataTemplate within the ListBox control/other panel.
Up to this point I have tried the following with no avail:
Thanks in advance for any tips.
<!--within Window.Resource -->
<DataTemplate x:Key="myActivity">
<local:ucActivityItm /> <!--usercontrol -->
</DataTemplate>
<!-- Listbox within the window -->
<ListBox HorizontalAlignment="Stretch" ItemTemplate="{DynamicResource myActivity}" VerticalAlignment="Stretch">
<ListBoxItem>
<!-- control also added for testing to ensure it rendered out-->
<local:ucActivityItm />
</ListBoxItem>
</ListBox>
That DataTemplate isn't actually being assigned to your ListBox. There's three ways:
1: Replace the template in the Resources section with
<ListBox.ItemTemplate>
<DataTemplate>
<local:ucActivityItm />
</DataTemplate>
</ListBox.ItemTemplate>
in the ListBox.
2: Somewhat related:
<ListBox ... ItemTemplate="{StaticResource myActivity}">
3: Set the DataType parameter of the DataTemplate above to whatever the content of your ListBox is.
<DataTemplate x:Key="myActivity" DataType="{x:Type ...}">
I usually just do the first, but any should work.

WPF: Problem with ItemsControl datatemplate as a user control

This works fine:
<ItemsControl ItemsSource="{Binding Persons}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=FirstName}"/>
<TextBlock Text="{Binding Path=LastName}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But, I want to make use of a User Control in place of the stack panel with two TextBlocks using something like:
<DataTemplate>
<jasControls:NameView/>
</DataTemplate>
But, with this and a million other syntaxes, the NameView shows nothing, yet it works fine in a separate test, outside the ItemsControl, with explicitly set (dependency) property - e.g.
<jasControls:NameView FName="{Binding Path=Person.FirstName}" />
I can find hardly any examples of this anywhere, and need to know how to specify the properties of the user control - or how can the user control 'receive' the individual item type (a Person)? What syntax to use? Do I need to specify the datatype? Is ItemsControl causing a problem or should any similar control do e.g. ListBox? Can I design it so that the user control gets an entire Person object? What dependency properties do I need in the user control? In short, how to get data into the user control?! The actual business types involved will be a bit more complicated involving some logic in the user control, hence my desire to use a user control.
ANY direction on this will be very gratefully received - TIA
Assuming your Persons collection has many Person objects in it, the DataContext property of your NameView control will automatically be set to the Person object.
You wouldn't need any dependency properties to achieve this feat. Your NameView usercontrol would simply be:
<UserControl ...>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text="{Binding LastName}"/>
</StackPanel>
</UserControl>
You should have no codebehind to get this information to display.
From there, you should be able to access the Person object from the DataContext property:
Person person = this.DataContext as Person;

How to bind items of a TabControl to an observable collection in wpf?

What is the simplest example of binding the items of a TabControl to an ObservableCollection?
Each tab's content will have unique data, and indeed this data will have observableCollections of its own bound to the items components.
Currently I have a user control, which I would like to set as the content of each tab as soon as it is created. I also need to dynamically set the datacontext of this new user control when the tab is created. So, essentially, I would like the tabcontrol's observablecollection contain modelviews that map to the data in each tab.
On top of that, I need to do all this without violating MVVM in WPF! Any help?
Much appreciated!
Basic example :
<Window.Resources>
<DataTemplate x:Key="templateForTheContent" DataType="{x:Type vm:TheViewModelType}">
<v:YourUserControl/>
</DataTemplate>
<DataTemplate x:Key="templateForTheHeader" DataType="{x:Type vm:TheViewModelType}">
<TextBlock Text="{Binding ThePropertyToDisplayInTheHeader}"/>
</DataTemplate>
</Window.Resources>
...
<TabControl ItemsSource="{Binding YourCollection}"
ContentTemplate="{StaticResource templateForTheContent}"
ItemTemplate="{StaticResource templateForTheHeader}">
</TabControl>

Resources