I have a listbox in WPF that will contain a list of ResultsViewModel items, however the actual runtime type of these objects could be
CalculateResultsViewModel,
ScenarioResultsViewModel,
GraphResultsviewModel etc etc,
all of which extend the base abstract class ResultsViewModel.
Each of these view models should be rendered differently in the ListBox so needs a different DataTemplate. I can do that just with XAML easy enough. The difficulty is that when the viewmodels are either "processing" or when they have failed", I need them to display a DataTemplate for "processing" or "errored" which I can only so far do with Triggers. That however then means I can't use the DataTemplateSelector or a basic XAML style.
The only solution I can think of (not clean I know) is to set the DataTemplate programmatically in the SetResult() method of each viewmodel class, which is what gets called when the processing completes either successfully or with an error. In that DependencyProperty I can look at the return code and then programatically set the DataTemplate depending on the sucess/failure result. The only problem is I cannot figure out how to
Obtain a DataTemplate resource from a ResourceDictionary just using c# code - bearing in mind Im calling all of this from the viewmodel class, not the window code-behind .xaml.cs file so it doesn't have access to the properties of Window
having only a handle to the viewmodel class, somehow obtain a reference to the ListBoxItem that contains it and then programmatically set the DataTemplate on this container.
Can anyone point me in the right direction?
you can take the magic with implicit datatemplates
<ListBox ItemSource={Binding YourResults}>
<ListBox.Resources>
<DataTemplate DataType={x:Type CalculateResultsViewModel}>
<Grid></Grid>
</DataTemplate>
<DataTemplate DataType={x:Type ScenarioResultsViewModel}>
<Grid></Grid>
</DataTemplate>
<DataTemplate DataType={x:Type GraphResultsviewModel }>
<Grid></Grid>
</DataTemplate>
</ListBox.Resources>
</ListBox>
for "processing" or "errored" viewmodels you can specify a adorner overlay in all yout datatemplates (ok but you must use the triggers)
hope this helps
Related
On a project I have started using Caliburn.Micro.
Now I want to split a large UserControl into several smaller UserControls, so that I can use them inside of DataTemplates. EisenbergEffect suggests to do so in this answer
The ViewModels for those are already there, as I modelled the hierachical data as ObservableCollection<SubViewModel>.
Now I thought I just have to create the suiting Views as UserControls for those SubViewModels.
My view is a Master-Details view. I want to present a list of Computers and in the Details View I want to have the HardwareComponents of those.
<ListView x:Name="ComputerViewModels">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl x:Name="HardwareComponentViewModel" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have included Debug output as suggested here: Debug Logging
And I do not get any output about my HardwareComponentViewModel except
Action Convention Not Applied: No actionable element for set_HardwareComponentViewModel
The HardwareComponentViewModels already get created by the time the ComputerViewModels get created.
I already use the ContentControl - way on other parts of the application and it works very well there (getting the Content using IOC injected into the outer ViewModel).
But it does not seem to fit for the DataTemplate part (which is not very surprising, though). The SubViewModels are derived from Screen, as the outer ViewModels.
How can I use the new UserControl as DataTemplate?
Take a close look at EisenbergEffect's answer. It's explicitly stated, that conventions don't work inside DataTemplate, so if you have a complicated template, it's better to move it out to a separate UserControl, inside which conventions will work again.
That means you have to explicitly bind the model inside the template:
<ListView x:Name="ComputerViewModels">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding HardwareComponentViewModel}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
That should do it. Either that or cal:Model.Bind="{...}", I can never remember the difference and use case. Now inside the bound UserControl (HardwareComponentView, I presume) the conventions between view and the view model should work as usual.
I am trying the following in my WPF application:
Structure of XAML elements.
DataTemplate[Data Type 'A']
Grid
Broder
TextBlock
I want to bind the text block's text property to a "string" which is derived from my "Users" class which is referenced in the resource dictionary of the XAML.
So in the above structure since the DataTemplate gets the feed from data type 'A'.
I want to assign the datacontext(Users) to the grid and bind the string to the textblock.
Is there a way i can achieve this ,since all my trials which include assigning the datacontext to the Grid or Border or TextBlock doesn't work.
Can any one suggest me or correct me if my approach is wrong here ?
This markup should suffice:
<DataTemplate DataType="{x:Type local:A}">
<Grid DataContext="{Binding Path=Users}">
<Border>
<TextBlock Text="{Binding Path=PropertyOnUsers}"/>
</Border>
</Grid>
</DataTemplate>
Make sure you have the namespace declared at the top of your Xaml. For whatever reason, WPF doesn't always automatically infer the template from the type if you don't use {x:Type ...}.
From there it should be straight forward.
If Users is a collection, you will have to drill into the collection to get a specific instance of User.
By the way, if you are using Visual Studio, you can use the Output window to debug binding issues.
Let me explain you my situation.
I have a base class called Shape, and several concrete classes like Triangle, Square, etc.
I have several data templates.
I'm building just one object. So I wouldn't use an ItemControl control, I would like to use a normal panel like the grid, and show the respective data template (in DataContext has the concrete item)..
The only way to do this is using an ItemsControl? Or there's another way.. because I'm just using one item and not a collection and display the correct template.
DataTemplates are used in much more than just ItemsControls
They are used to tell WPF how to draw any object in the Visual Tree. For example, if you stick a User class object in the VisualTree, a DataTemplate can be used to tell WPF how to draw that User object
They are most frequently used in controls with an ItemsSource or Content properties, because those are the most common way of inserting data objects into the VisualTree.
In your specific case where you only want to insert one data item into the VisualTree, I would suggest a ContentControl
<ContentControl Content="{Binding MyDataObject}" />
To tell WPF how to draw MyDataObject you can either use the ContentTemplate property and set it to a DataTemplate
<ContentControl Content="{Binding MyDataObject}"
ContentTemplate="{StaticResource MyDataTemplate}" />
or define an implicit DataTemplate that tells WPF to draw any object of a specific type using a specific template.
<DataTemplate DataType="{x:Type local:MyDataObject}">
<!-- Tell WPF how to draw MyDataObject here -->
</DataTemplate>
If you want to display a single item with a data template that is selected based on the item's type, you should use ContentControl or any of its derived classes.
I'm trying to choose the best way to implement this UI in MVVM manner. I'm new to WPF (like 2 month's) but I have huge WinForms experience.
The ListBox here act's like a TabControl (so it switches the view to the right), and contains basically the Type of item's displayed in tables. All UI is dynamic (ListBox items, TabItems and Columns are determined during run-time). The application is targeting WPF and Silverlight.
Classes we need for ViewModel:
public abstract class ViewModel : INotifyPropertyChanged {}
public abstract class ContainerViewModel : ViewModel
{
public IList<ViewModel> Workspaces {get;set;}
public ViewModel ActiveWorkspace {get;set;}
}
public class ListViewModel<TItem> where TItem : class
{
public IList<TItem> ItemList { get; set; }
public TItem ActiveItem { get; set; }
public IList<TItem> SelectedItems { get; set; }
}
public class TableViewModel<TItem> : ListViewModel<TItem> where TItem : class
{
public Ilist<ColumnDescription> ColumnList { get; set; }
}
Now the question is how to wire this to View.
There are 2 base approaches I can see here:
With XAML: due to lack of Generics support in XAML, I will lose strong typing.
Without XAML: I can reuse same ListView<T> : UserControl.
Next, how to wire data, I see 3 methods here (with XAML or without doesn't matter here). As there is no simple DataBinding to DataGrid's Columns or TabControl's TabItems the methods I see, are:
Use DataBinding with IValueConverter: I think this will not work with WPF|Silverlight out of the box control's, as some properties I need are read-only or unbindable in duplex way. (I'm not sure about this, but I feel like it will not work).
Use manual logic by subscribing to INotifyPropertyChanged in View: ViewModel.PropertyChanged+= ....ViewModel.ColumnList.CollectionChanged+= ....
Use custom controll's that support this binding: Code by myself or find 3d party controls that support this binding's (I don't like this option, my WPF skill is too low to code this myself, and I doubt I will find free controls)
Update: 28.02.2011
Things get worser and worser, I decided to use TreeView instead of ListBox, and it was a nightmare. As you probably guess TreeView.SelectedItems is a readonly property so no data binding for it. Ummm all right, let's do it the old way and subscribe to event's to sync view with viewmodel. At this point a suddenly discovered that DisplayMemberPath does nothing for TreeView (ummmm all right let's make it old way ToString()). Then in View's method I try to sync ViewModel.SelectedItem with TreeView's:
private void UpdateTreeViewSelectedItem()
{
//uiCategorySelector.SelectedItem = ReadOnly....
//((TreeViewItem) uiCategorySelector.Items[uiCategorySelector.Items.IndexOf(Model.ActiveCategory)]).IsSelected = true;
// Will not work Items's are not TreeViewItem but Category object......
//((TreeViewItem) uiCategorySelector.ItemContainerGenerator.ContainerFromItem(Model.ActiveCategory)).IsSelected = true;
//Doesn't work too.... NULL // Changind DataContext=Model and Model = new MainViewModel line order doesn't matter.
//Allright.. figure this out later...
}
And none of methods I was able to think of worked....
And here is the link to my sample project demonstrating Control Library Hell with MVVM: http://cid-b73623db14413608.office.live.com/self.aspx/.Public/MVVMDemo.zip
Maciek's answer is actually even more complicated than it needs to be. You don't need template selectors at all. To create a heterogeneous tab control:
Create a view model class for each type of view that you want to appear as tab items. Make sure each class implements a Text property that contains the text that you want to appear in the tab for its item.
Create a DataTemplate for each view model class, with DataType set to the class's type, and put the template in the resource dictionary.
Populate a collection with instances of your view models.
Create a TabControl and bind its ItemsSource to this collection, and add an ItemTemplate that displays the Text property for each item.
Here's an example that doesn't use view models (and that doesn't implement a Text property either, because the objects I'm binding to are simple CLR types), but shows how template selection works in this context:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:coll="clr-namespace:System.Collections;assembly=mscorlib">
<DockPanel>
<DockPanel.Resources>
<coll:ArrayList x:Key="Data">
<sys:String>This is a string.</sys:String>
<sys:Int32>12345</sys:Int32>
<sys:Decimal>23456.78</sys:Decimal>
</coll:ArrayList>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock Text="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Int32}">
<StackPanel Orientation="Horizontal">
<TextBlock>This is an Int32:</TextBlock>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Decimal}">
<StackPanel Orientation="Horizontal">
<TextBlock>This is a Decimal: </TextBlock>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</DockPanel.Resources>
<TabControl ItemsSource="{StaticResource Data}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</DockPanel>
</Page>
Of course in a real MVVM application those DataTemplates would use UserControls to map each type to its view:
<DataTemplate DataType="{x:Type my:ViewModel}">
<my:View DataContext="{Binding}"/>
</DataTemplate>
Maciek and Robert already gave you some ideas on how to implement this.
For the specifics of binding the columns of the DataGrid I strongly recommend Meleak's answer to that question.
Similar to that you can use attached properties (or Behaviors) and still maintain a clean ViewModel in MVVM.
I know the learning curve for WPF is quite steep and you're struggling already. I also know that the following suggestion doesn't help that and even makes that curve steeper. But your scenario is complex enough that I'd recommend to use PRISM.
I wrote an article and a sample application with source code available, where I discuss and show the problems I have mentioned here and how to solve them.
http://alexburtsev.wordpress.com/2011/03/05/mvvm-pattern-in-silverlight-and-wpf/
In order to connect your ViewModel to your View you need to assign the View's DataContext. This is normally done in the View's Constructor.
public View()
{
DataContext = new ViewModel();
}
If you'd like to see your view model's effect at design time, you need to declare it in XAML, in the View's resources, assign a key to it, and then set the target's DataContext via a StaticResource.
<UserControl
xmlns:vm="clr-namespace:MyViewModels
>
<UserControl.Resources>
<vm:MyViewModel x:Key="MyVM"/>
</UserControl.Resources>
<MyControl DataContext={StaticResource MyVM}/>
</UserControl>
(The above is to demonstrate the design-time trick works)
Since you're dealing with a scenario that includes a container such as the TabControl I'd advocate considering the following things :
Hold your child ViewModels in a Property of type ObservableCollection
Bind the TabControls ItemsSource to that property
Create a new View that derives from TabItem
Use a template selector to automatically pick the type of the view based on the type of the view model.
Add IDisposable to yoour child ViewModels and create functionality to close the views.
Hope that helps a bit, if you have further questions let me know.
I'm building a custom WPF control that derives from TabControl. In the ControlTemplate, I'm using a ItemsControl to display a list that is being bound from the template (an observable collection of type FileMenuItem). During program execution, I'm getting the following error in the output window:
ItemTemplate and ItemTemplateSelector
are ignored for items already of the
ItemsControl's container type;
Type='FileMenuItem'
The type FileMenuItem is derived from MenuItem. If I change the base class to DependencyObject, the code actually runs and the template is applied (so that's an option). I googled the error and couldn't find anything about it, has anyone run into this while developing custom controls? Even though I have a workaround, I'd like to understand what's happening, and I think using the MenuItem as a base class is a cleaner implementation.
I can post more code if it would help. Thanks!
The purpose of a DataTemplate (like ItemTemplate) is to provide a visualization for a data object. Specifically, it defines a set of elements to add to the visual tree in place of the data given to an ContentPresenter or ItemsPresenter. In your case your source list is a collection of objects that are already able to be added directly to the visual tree for display in the UI.
You can see this in the following simplified example where only "Three" shows up in Red because the first two items are defined in a form that can be displayed directly by ComboBox.
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Foreground="Red"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBoxItem>One</ComboBoxItem>
<ComboBoxItem>Two</ComboBoxItem>
<sys:String>Three</sys:String>
</ComboBox>