Get context in which a view is rendered? - wpf

In my WPF application I have a viewmodel class called CompanyViewModel.
Sometimes, an instance of this class is set as the DataContext of my main window, which is defined like this:
<window x:Class= ..... >
<Grid>
<ContentControl Content="{Binding }"></ContentControl>
</Grid>
</Window>
In this case I want a view to be used that displays all the properties of the viewmodel.
Other times, a ListView control has its itemsource set as a collection containing instances of CompanyViewModel. Here, I want a view to be used that renders only some important properties.
I have this in the resource dictionary of MainWindow.xaml:
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyView></vw:CompanyView>
</DataTemplate>
Is it possible to select a view for the viewmodel based on the context where the viewmodel is bound? For instance, to use CompanyView when displayed in the ContentControl of a window or when in a TabControl, and to use CompanyViewSmall where displayed in a ListView?

The DataTemplate to use is first looked for locally, and then looked for further up the Visual Tree hierarchy if it's not found.
Because of this, you can specify the DataTemplate to use further down the hierarchy to use something different than normal.
For example, the following will use the CompanyView anywhere the CompanyViewModel is in the visual tree, except in the specific ListView where the DataTemplate is specified as the smaller view.
<Window.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyView />
</DataTemplate>
</Window.Resources>
<ListView>
<ListView.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyViewSmall />
</DataTemplate>
</ListView.Resources>
</ListView>
You could also use an implicit style for the ListView telling it to use the smaller template in the .Resources, however this will apply the smaller view to any ListView, not just specific ones, and if you ever apply another style to a ListView you'll have to remember to inherit the default style to keep the smaller DataTemplate.
<Style TargetType="{x:Type ListView}">
<Style.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyViewSmall />
</DataTemplate>
</Style.Resources>
</Style>

Related

In a tree view with multiple DataTemplates, how to apply a style to only one of them

I have a TreeView control that shows different tyeps of objects. I use multiple DataTemplates, one per type, with their DataType set accordingly.
Code:
<TreeView>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Family}" ItemsSource = "Members">
<!-- template omitted here-->
</HierarchicalDataTemplate>
<DataTemplate DataType = "{x:Type local:FamilyMember}">
<!--template omitted-->
</DataTemplate>
</TreeView.Resources>
</TreeView>
Now I want to apply a style to the HierarchicalDataTemplate and only to it. I must use a style because I set a few properties of the TreeViewItem, which is in this case the items container.
I tried:
<HierarchicalDataTemplate DataType="{x:Type local:Family}" ItemsSource = "Members">
<HierarchicalDataTemplate.ItemsContainerStyle>
<Style TargetType = "TreeViewItem">
<!-- some styling of the tree view item-->
</Style>
<\HierarchicalDataTemplate.ItemsContainerStyle>
<!-- template omitted here-->
</HierarchicalDataTemplate>
but the style gets applied to all tree items, even those presenting FamilyMember objects, which are not of the same template.
How can I do it?
You can use the ItemContainerStyleSelector property to control which styles get applied to which items. I found an example usage in this answer that might help you get started. The main difference from that example is that you would base your selection on the type of the object rather than a property of the object.

How to support multiple views with the same viewmodel?

What is the best way of using the same viewmodel to support multiple views?
(Yes..I have read "MVVM (with WPF)-Binding Multiple Views to the SameViewModel", but if I read it correctly, the answer involved creating different viewmodels which is not what I want to do here).
In the below code the StringViewModel supports the InkStringView when the InkStringView consists of defined rows. I wish now to define a second view that consists of columns but must keep the same datacontext of StringViewModel. In the second view, the controls have different positions and sizes that the StringViewModel calculates but thier function and purpose remain the same. It would be ideal if the module that creates the StringViewModels in Strings, an observablecollection, does not know what view will be used leaving the final decision to the xaml of the usercontrol.
My question is how to design either the StringViewModel and/or the DataTemplate to allow for different views and calculations based on that view by changing only the DataTemplate.
(I tried inheriting the StringViewModel to different viewmodels, each viewmodel specific to its view, but it did not work).
Thanks in advance for any help or suggestions. Or is their a better way?
Example:
<DataTemplate DataType="{x:Type vm:StringViewModel}">
<v:InkStringView_2 /> <----CHANGING THE VIEW TO COLUMNS. ViewModel needs
</DataTemplate> to perform calculations specific to the view.
The usercontrol is:
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:StringViewModel}">
<v:InkStringView />
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl
ItemsSource="{Binding Strings}" >
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Margin" Value="{Binding Margin}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</UserControl>
just a suggestion:
you have a viewmodel MyViewmodel which you wanna display with different DataTemplates. then for me a easy way would be to create "new" Classes with "no" implementation
public class MyT1 : MyViewmodel {}
public class MyT2 : MyViewmodel {}
public class MyT3 : MyViewmodel {}
so now all MyT1, MyT2, MyT3 have the same methods and stuff, but you can create a datatemplate foreach of them
<DataTemplate DataType="{x:Type vm:MyT1}">
<v:T1View />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MyT2}">
<v:T2View />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MyT3}">
<v:T3View />
</DataTemplate>
or you dont go the Viewmodel First approach and do View First and then you can choose the view you want with your Datacontext
I've had a similar problem, the way I've tackled it is by using ControlTemplate so:
In your View:
<Control>
<Control.Resources>
<Style TargetType="Control">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<!-- Here you put you view it could be a UC if you want -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style
</Control.Resources>
</Control>
Then in your Style.Resources you can define Triggers or DataTriggers to change the Template property of your Control. This way you keep the ViewModel as it is and you just change the Views that are getting the data. Hope this makes sense, if anything give us a shout and I'll put more info in.
HTH
Not an answer, but a simple solution.
Redefine Strings, the ItemsSource, as
ObservableCollection<StringViewModelBase> Strings
then create children of StringViewModelBase as
public class StringByRowViewModel : StringViewModelBase
then change the DataTemplate in the resources to be
<DataTemplate DataType="{x:Type vm:StringByRowViewModel}">
<v:InkStringByRowView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:StringByColumnViewModel}" >
<v:InkStringByColumnView />
</DataTemplate>
and lastly, I see no choice but to change the module that builds the Strings (at least as far as the viewmodel goes). Instead of
StringViewModelBase svm = new StringViewModelBase(text,color, w);
use
StringByColumnViewModel svm = new StringByColumnViewModel(text,color,w);
with
Strings.Add(svm);
Apparently, the ObservableCollection only needs a common parent and the Data Type is still preservered in the XAML.

How do I apply a Style Setter for ListBoxItems of a certain DataType?

My WPF ListBox contains two types of object: Product and Brand.
I want my Products selectable. I want my Brands not selectable.
Each of the two types has its own DataTemplate.
By default, anything may be selected:
<ListBox ... >
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:Product}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type local:Brand}">
...
</DataTemplate>
</ListBox.Resources>
</ListBox>
I can set Focusable with a Setter, but then nothing may be selected:
<ListBox ... >
<ListBox.Resources>
...
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Focusable" Value="False" />
</Style>
</ListBox.Resources>
</ListBox>
I cannot put the Setter within the DataTemplate.
I cannot put a DataType onto the Style.
How do I style only the ListBoxItems of type Brand?
Thanks to the StyleSelector class you can attach styles depending on type of data for the ItemContainerStyle. There is a really good example here : http://www.telerik.com/help/wpf/common-data-binding-style-selectors.html
Can you use a data trigger on your ListBoxItem style? If so, bind to the DataContext (your class) and use a value converter to test the type. If it's the one you're interested in, style the ListBoxItem so that it cannot appear selected.
I don't think you can disallow selection of an item in a Selector (parent of ListBox) without codebehind or a custom Behavior.

Can I get strongly typed bindings in WPF/XAML?

Using the MVVM-pattern you set the DataContext to a specific ViewModel. Now is there any way to tell the XAML the type of the DataContext so that it will validate my bindings?
Looking for something like the typed viewdata in ASP.NET MVC.
You can write each individual binding in a strongly-typed way:
<TextBox Text="{Binding Path=(vm:Site.Contact).(vm:Contact.Name)}" />
However this would not validate the fact that TextBox DataContext is of type ViewModel.Site (and I think this is not possible, but I may be wrong).
No, the current spec does not have strong typing in Xaml. I believe that with .Net 4.0, Xaml should be seeing the capacity for generics. With that, I would think it should be much easier to have strong typing in Xaml.
No. FrameworkElement.DatatContext is the dependency property that enables data binding is of type object.
As pointed out by others, you can specify the expected type of a DataContext for a special template called a DataTemplate. Many controls such as ItemsControl, ControlControl provide access to DataTemplates to allow you to set the visual representation's expectations of the DataContext's type.
Bryan is correct, he did not test his code.
The correct application of a typed DataTemplate looks like this:
<Window>
<Window.Resources>
<DataTemplate x:Key="TypedTemplate" DataType="{x:Type myViewModel}">
...
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource TypedTemplate}" />
</Window>
ContentPresenter inherits directly from FrameworkElement and does not have a Template property. In addition, the Template property commonly refers to Control.Template of type ControlTemplate which is something entirely different than a DataTemplate.
I think Bryan was thinking of the ContentControl which is one of the two root control types (the other being ItemsControl). ContentControl does in fact inherit from Control. Therefore we can specify the Template property on it if we so choose.
<Window>
<Window.Resources>
<DataTemplate x:Key="TypedTemplate" DataType="{x:Type myViewModel}">
...
</DataTemplate>
<ControlTemplate x:Key="ControlSkin" TargetType="{x:Type ContentControl}">
...
</ControlTemplate>
</Window.Resources>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource TypedTemplate}" Template="{StaticResource ControlSkin}" />
</Window>
I personally declare a static PropertyPath for each property in my viewmodel the reference this using x:static as the binding path -
e.g
public class MyViewModel
{
public static PropertyPath MyPropertyPath = new PropertyPath("MyProperty");
public bool MyProperty{get; set;}
}
xaml : {Binding Path={x:Static local:MyViewModel.MyPropertyPath}}
This way all my bindings get validated on build.
Try this:
<Window>
<Window.Resources>
<DataTemplate x:Key="TypedTemplate" DataType="{x:Type myViewModel}">
...
</DataTemplate>
</Window.Resources>
<ContentPresenter Content="{Binding}" Template="{StaticResource TypedTemplate}" />
</Window>
I haven't tested this code but it should give you the idea. The content presenter will display the current DataContext which will use the DataTemplate. This isn't strongly typed in the compiler but will throw a runtime error immediately on load (in the window's InitializeComponent). You should be able to catch this easily in your testing if something breaks.

Exposing a sub-control in a UserControl for use in XAML

I have a UserControl that contains a TreeView. I want the user to be able to set the properties of the inner TreeView control via XAML and I'm not sure how to do that.
I've tried creating a public property on the UserControl to the TreeView, but that only allows me to set a SelectedItemChanged trigger.
I'd like to do something like:
<ExampleUserControl>
<ExampleUserControl.TreeView.ItemTemplate>
...
</ExampleUserControl.TreeView.ItemTemplate>
</ExampleUserControl>
Or:
<ExampleUserControl TreeView.ItemsSource="{Binding Foo}" />
I would prefer not to create properties in the UserControl for each TreeView property, and I don't want to force the user to define the control in C#.
As for passing multiple properties to the child control in your user control, you can always expose a Style property.
ie ChildStyle
For the ItemsSource unless you use [Josh Smith's Element Spy / Data Context Spy / Freezable][1] trick, you will have a disconnect on DataContexts.
So either you employ those tricks or simply have 2 properties.
1) the ItemsSource
2) the ChildStyle
The xaml ends up...
<ChildTreeAnswer:MyControl ItemsSource="{Binding Items}">
<ChildTreeAnswer:MyControl.ChildStyle>
<Style>
<Setter Property="ItemsControl.ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black"
BorderThickness="1"
Margin="5">
<TextBlock Text="{Binding }" />
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ChildTreeAnswer:MyControl.ChildStyle>
</ChildTreeAnswer:MyControl>
Then in your user control do... (I used a listbox for simplicity sake)
<ListBox ItemsSource="{Binding ItemsSource}"
Style="{Binding ChildStyle}" />

Resources