WPF organizing data templates - c#

I'm writing a WPF application. I want it to display data in ListBox from different sources. I want to make some common source interface like
interface IDataSource<T>
{
ObservableCollection<T> Elements { get; set; }
DataTemplate ElementDataTemplate { get; set; }
}
But I don't know which is the best type or types which I should user for IDataSource. I can make it UserControl, but it seems to be unnecessary, because my DataSource is not user control. The main problem is with ElementDataTemplate. How can I properly manage it not from UserControl class? Should I care another helper UserCntrol class and call something like (new MyUserControl).FindResource("ElementsDataTemplate") to obtain datatemplate or there is more fine way to keep and get DataTemplate?

You can simply apply a data template for a specific type in the resource section of the corresponding view:
<!-- Items may be of type ViewModel1 and ViewModel2 -->
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type vm:ViewModel1}">
<TextBlock Text="{Binding PropertyA}" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ViewModel2}">
<TextBlock Text="{Binding PropertyB}" />
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
So there is no need for the interface.

Related

It is possible to Have ObservableCollection of ObservableCollections in viewModel using MVVM pattern

I wonder to know if it is possible to have an ObservableCollection of ObservableCollections in viewModel like this:
ObservableCollection<ObservableCollection<EditingMetadataViewModel>> MetadatasList = new ObservableCollection<ObservableCollection<EditingMetadataViewModel>>();
each ObservableCollection shows a list of metadata when it is binded to the view. In the case I have more than one file selected I want to have the same metadata lists number as selected files number (e.g if I select three files and I want to edit their metadata, I want to have three lists of metadata list).
Yes you can create a ItemsControl(or Panel controls) of ListView, ListBox, Grid, ... as child items:
<ItemsControl ItemsSource="{Binding MetadatasList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding MetaData}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I don't see why not and would be very easy for you to verify this. You could quickly create a ListBox that has a data template that contains a ListBox.
For clarity however you may want to define a class FileMetadata. In that case the first observable collection will be declared as
public ObservableCollection<FileMetadata> FileMetadataList {get; private set; }
Class FileMetadata would contain a member:
public ObservableCollection<EditingMetadataViewModel>> MetadatasList {get; private set; }
This is equivalent with your code but will may make certain parts easier to read and manage.

Creating a ViewModel for each item of ItemsControl

I have defined a type Board which containes a public property
ObservableCollection<Column> Columns
I would like to display it with use of MVVM pattern.
I created BoardView and bound it to BoardViewModel. BoardViewModel exposes public property Board of type Board.
BoardView contains a control ItemsControl which sets ItemsSource={Binding Board.Columns}.
<ItemsControl ItemsSource="{Binding Board.Columns}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" Margin="10" BorderBrush="#9f9f9f" Width="250">
<v:BoardColumnView Background="#e3e3e3" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
BoardColumnView should show properties of Column type and this works good.
My problem is that I want to create a ViewModel for BoardColumn, and instead of showing only properties of Column type I want to show BoardColumnViewModel which would have defined inside a Column property.
How can I achieve that?
thanks in advance!
You could simply define a Columns property in your BoardViewModel that would contain a collection of BoardColumnViewModel. Something like this:
public ObservableCollection<BoardColumnViewModel> Columns { get; private set; }
You will need to initialize this property somewhere in BoardViewModel, for example:
public BoardViewModel(...)
{
...
Columns = new ObservableCollection<BoardColumnViewModel>(Board.Columns.Select(c => new BoardColumnViewModel(c)));
}
And then bind to that property, instead of Board.Columns:
<ItemsControl ItemsSource="{Binding Columns}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" Margin="10" BorderBrush="#9f9f9f" Width="250">
<v:BoardColumnView Background="#e3e3e3" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
As a general principle in MVVM is that it is not recommended to bind directly to a model. Instead you should always try to bind to a view model. This is why you have the problem you've described - because you bind to a public property Board, which is your model.
In my understanding is BoardColumnView an own UserControl?
If so just set the DataContext of this UserControl with this.DataContext = new BoardColumnViewModel(); in code behind or use XAML equivalent. Then create in your BoardViewModel an ObserveableCollection<> which holds multiple Instances of BoardColumnView and set the ItemSource to this collection. In your BoardColumnView XAML define your Layout, Bindings, etc. which are displayed in BoardView.
So every time you add a BoardColumnView to the ItemsSource, which is bound to the collection, a new instance of BoardColumnViewModel is getting created.
...That is my understanding of your Problem, but I might be completely wrong :).

WPF Creating different ListBox row templates based on a bound value

I do not have any code at the moment to share. Just a design question.
I have a class that defines a label, and an associated entry type, which I would like to bind to a ListBox. If the type is for example, "Postal Code", I need the ListBox to create the row as a TextBlock and a TextBox. For "Yes/no", I need it to know instead to create a TextBlock with a CheckBox beside it. There will likely be 7 or 8 of these different row types.
What is the best way to approach this?
Have a look at the ItemTemplateSelector Property. This property allows you to provide custom logic for choosing which template to use for each item in a collection.
First define your various templates in a resource dictionary...
<Application>
<Application.Resources>
<DataTemplate x:Key="TextBoxTemplate">
<!-- template here -->
</DataTemplate>
<DataTemplate x:Key="CheckBoxTemplate">
<!-- template here -->
</DataTemplate>
</Application.Resources>
</Application>
Then, create a custom DataTemplateSelector...
public class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var myObj= item as MyObject;
if (myObj != null)
{
if (myObj.MyType is PostalCode)
{
return Application.Resources["TextBoxTemplate"] as DataTemplate;
}
if (myObj.MyType is YesNo)
{
return Application.Resources["CheckBoxTemplate"] as DataTemplate;
}
}
return null;
}
}
Then, its just a matter of using the ItemTemplateSelector property...
<Window>
<Window.Resources>
<local:MyTemplateSelector x:Key="tempSelector" />
</Window.Resources>
<ListBox ItemSource="{Binding items}" ItemTemplateSelector="{StaticResource tempSelector}" />
</Window>
You can use the DataTrigger class.
A DataTrigger allows you to set property values when the property value of the data object matches a specified Value.
Alternatively, you can use the DataTemplateSelector class.
Typically, you create a DataTemplateSelector when you have more than one DataTemplate for the same type of objects and you want to supply your own logic to choose a DataTemplate to apply based on the properties of each data object.
The best way to approach this would be to have a collection property containing all of the items that you want to see in your ListBox, bind that collection to a control that displays lists of items, and use different data templates to change the visuals used for each type of item.
For example, you might have a postal code type:
public class PostalCodeEntry
{
public string Value { get; set; } // Implement using standard INotifyPropertyChanged pattern
}
And a "Boolean" type:
public class BooleanEntry
{
public bool Value { get; set; } // Implement using standard INotifyPropertyChanged pattern
}
You said you wanted a label for each entry type, so a base class would be a good idea:
public abstract class EntryBase
{
public string Label { get; set; } // Implement using standard INotifyPropertyChanged pattern
}
Then BooleanEntry and PostalCodeEntry would derive from EntryBase.
That's the Models sorted. You just need a collection of these so that you can bind to them from the UI. Add an appropriate collection property to your Window or ViewModel:
public ObservableCollection<EntryBase> Entries { get; private set; }
In your UI (the View, implemented in XAML), use an instance of a control that knows how to bind to a list of items and visualize them. In WPF this would be an ItemsControl or a control that derives from it such as ListBox or ListView:
<ItemsControl ItemsSource="{Binding Entries}" />
You can see how we bind the ItemsSource property to our code-behind property named Entries. The ItemsControl (and its descendants) knows how to convert those items into a visual representation. By default, your custom object (EntryBase in our example) will be converted into a string and displayed as a text block. However, by using data templates you can control how the conversion from object to visual happens.
If you add a couple of data templates to the resources section like so:
<Window ... xmlns:my="clr-namespace:---your namespace here---">
<Window.Resources>
<DataTemplate DataType="{x:Type my:PostalCodeEntry}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Label}" />
<TextBox Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type my:BooleanEntry}">
<CheckBox Content="{Binding Label}" IsChecked="{Binding Value}" />
</DataTemplate>
</Window.Resources>
Then add the <ItemsControl ... element after that, then you should see a TextBlock/TextBox combo for PostalCodeEntry types and a CheckBox for BooleanEntry types.
Hopefully if you can get this working it will give you an idea how you could extend it to cope with other model types and their matching data templates.

WPF implicit datatemplate with observablecollection

I'm new to WPF and using MVVM. I have a view in which I want to display different content according to what a user selects on a menu. One of those things is another user control Temp which has a view model (TempVM) so I am doing this:
<ContentControl Content="{Binding Path=TempVM}"/>
and TempVM (of type TempViewModel)is null until the user clicks a button. Its data template is this
<DataTemplate DataType="{x:Type vm:TempViewModel}">
<view:Temp />
</DataTemplate>
That's fine, but the other thing I want to do is show a listbox when a user clicks a different menu item. So I am trying to do
<ContentControl Content="{Binding Path=Missions}"/>
(Missions is an observable collection of MissionData) and trying to template it like this:
<DataTemplate DataType="{x:Type ObservableCollection(MissionData)}">
<StackPanel>
<ListBox ItemsSource="{Binding}" SelectedItem="{Binding Path=MissionData, Mode=TwoWay}" DisplayMemberPath="MissionName" SelectedValuePath="MissionId" />
<Button Content="Go"/>
</StackPanel>
</DataTemplate>
But the compiler doesn't like the type reference. If I try doing it by giving the template a key and specifying that key in the ContentControl it works but obviously I see the ListBox and button when there's no Missions. Obviously I could make a user control and viewmodel and follow the same pattern as I did for the TempVM but it seems over the top. Am I going the right way about this and what do I need to do?
From what i see is that you try to use a Collection as a dataobject which is in my opinion bad practice. Having a DataTemplate for a collection is also problematic, like you already have witnessed. I would advice you to use a ViewModel for your missions collection.
class MissionsSelectionViewModel
{
public ObservableCollection<Mission> Misssions;
public MissionData SelectedMission;
public ICommand MissionSelected;
}
and modify your datatemplate to
<DataTemplate DataType="{x:Type MissionsSelectionViewModel}">
<StackPanel>
<ListBox ItemsSource="{Binding Missions}" SelectedItem="{Binding Path=MissionData, Mode=TwoWay}" DisplayMemberPath="MissionName" SelectedValuePath="MissionId" />
<Button Content="Go" Command="{Binding MissionSelected}/>
</StackPanel>
</DataTemplate>
If I were to follow your pattern of implicit templates, I would derive a custom non-generic collection MissionDataCollection from ObservableCollection<MissionData> and use it to keep MissionData items. Then I would simply reference that collection in DataType. This solution gives other advantages like events aggregation over the collection that are useful.
However, it seems to me that the best solution is the following.
Add a IsMissionsListVisible property to your VM.
Bind the Visibility property of the ContentControl showing the list to the IsMissionsListVisible property.
Use a keyed DataTemplate resource.
Implement the logic that determines if IsMissionsListVisible. Supposedly it should be true when there is at least one mission in the selected item. But the logic may be more complex.
I would do it this way. In fact, I do it this way usually, and it gives several benefits. The most important is that I can explicitly control the logic of content visibility in various situations (e.g. async content refresh).

How do you wire up views in Silverlight and MVVM using a TreeView?

I am building a Silverlight app which comprises a TreeView of menu options in a lefthand column and a ContentView in a righthand column. The idea is that the SelectedItemChanged event of the TreeView will change the view in the content area.
What is the 'purest MVVM' way of achieving this?
My idea is to have a TreeMenuView and TreeMenuViewModel for managing the menu events, but after that I'm a bit lost. I could use an EventAggregator to send a message from the TreeMenuViewModel to a `ContentViewModel' that would then set its current ContentView based on the message args- but surely that breaks MVVM, in the sense that a ViewModel shouldn't know about UI constructs like a View?
Am I missing something simple here?
How does a ViewModel layer drive the View selection?
I would create a ShellViewModel which had:
ObservableCollection<ViewModelBase> AvailablePages
int SelectedPageIndex
ViewModelBase CurrentPage, which returns AvailablePages[SelectedPageIndex]
Your ShellView can be anything you want. If you want to display your AvailablePages in a TreeView, then go ahead. Just remember to bind SelectedIndex to `SelectedPageIndex
In your case, I would create a DockPanel with a TreeView on the Left bound to AvailablePages, and a ContentControl on the right with ContentControl.Content bound to CurrentPage
Edit
Here's an example
<DockPanel>
<TreeView DockPanel.Dock="Right"
ItemsSource="{Binding AvailablePages}"
SelectedItem="{Binding SelectedPageIndex}">
...
</TreeView>
<ContentControl Content="{Binding CurrentPage}" />
</DockPanel>
Then use DataTemplates to define how the ContentControl containing CurrentPage will look
<Window.Resources>
<DataTemplate DataType="{x:Type local:HomePageViewModel}" />
<local:HomePageView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:CustomerViewModel}" />
<local:CustomerView />
</DataTemplate>
</Window.Resources>
Ok I give it a shot
in TreeMenuViewModel:
public string PropSelectedItem
{
get;
set;
}
in TreeMenuView:
<TreeView Context="{Binding TreeMenuViewModel}" Content="{Binding PropSelectedItem, Mode=OneWayToSource}"/>
in ContentViewModel:
public ViewModelBase PropSelectedItem
{
get
{
switch(TreeMenuViewModelStatic.PropSelectedItem)
{
case "Booo": return typeof(View1);
case "Foo": return typeof(View2);
}
}
private set;
}
in ContentView:
<ContentControl Context="{Binding TreeMenuViewModel}" Content="{Binding PropSelectedItem, Mode=OneWay}"/>
and you need a value convertor here

Categories