How to Binding data to User Control loaded in Control Template? - wpf

My CMainViewModel has a property called AnalogGaugeValue1
On my Window I have a data template as shown below. I use this template to load a user control named AnalogIOView and try to binding the dependency property, GaugeValue1DP, inside AnalogIOView to the AnalogGaugeValue1
<Window>
<Window.Resources>
<DataTemplate x:Key="AnalogIOViewTemplate" DataType="{x:Type local:CMainViewModel}">
<local:AnalogIOView
GaugeValue1DP="{Binding Path=AnalogGaugeValue1}" />
</DataTemplate>
</Window.Resources>
<Window>
I use the ContentControl to load the AnalogIOViewTemplate in my Window as shown below
<Grid>
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="
{StaticResource AnalogIOViewTemplate }" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
The user control displays correctly on my Window but I could not set the Analog Gauge value through binding in CMainViewModel.
However, If I just load the user control directly (without using Control Template) then it is working correctly as shown below
<Grid>
<local:AnalogIOView GaugeValue1DP="{Binding Path=AnalogGaugeValue1}" />
</Grid>
My question is why it does not work when loading the user control using Content Control template and data template
Thanks for your help

You have to set the Content property, and it would also be simpler not set an x:Key on the DataTemplate. It would then automatically be selected as the ContentTemplate of the ContentControl.
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type local:CMainViewModel}">
<local:AnalogIOView GaugeValue1DP="{Binding AnalogGaugeValue1}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding}"/>
</Grid>
<Window>

Related

Map control within Listbox

I am trying to display multiple maps within an Listbox.
<Grid Name="MainGrid">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Visible" >
<WrapPanel Name="wrap" >
<ListBox ItemsSource="{Binding MyList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border Margin="5" MinWidth="500" MinHeight="400" BorderThickness="2" BorderBrush="Black"
Width="200"
Height="200" >
<esri:MapView MouseDown="MapView_MouseDown" MouseUp="MapView_MouseUp" >
<esri:Map >
<esri:ArcGISTiledMapServiceLayer ID="BaseMap" ServiceUri="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"/>
</esri:Map>
</esri:MapView>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</WrapPanel>
</ScrollViewer>
</Grid>
The map does not get displayed. Only the logo "esri" gets displayed. But if I remove the listbox, it works fine. What could be the issue?
I am very sure that there are items in my listbox otherwise "esri" would not have appeared.
I have tried Itemscontrol as well but its the same result.
In WPF, we don't put UI elements into collections, we put data objects into collections. Each data item should contain whichever properties are needed to data bind to the UI control that you actually want to display. So your MyList collection should contain a collection of data objects.
Once you have created a custom class to contain these required properties, you will then need to declare a DataTemplate that defines which UI element(s) to display. (See the Data Templating Overview page on MSDN for further information on this).
<ControlTemplate x:Key="YourCustomControlTemplate">
<!-- Define your UI content here -->
</ControlTemplate>
Once you have declared your custom ControlTemplate for your custom data class, you should then put it into a Style for your data object:
<Style TargetType="{x:Type YourXamlPrefix:YourCustomDataClass}">
<Setter Property="Template" Value="{StaticResource YourCustomControlTemplate}" />
</Style>
Note that I have omitted the x:Key directive on this Style... you can add one, but without one (and as long as this Style has been declared in scope of the UI), the Style will be automatically applied to your data objects in the collection.

Styles on DataTemplates

I have an issue with using Styles on DataTemplates in WPF ... It appears to suck. Suppose that you define a DataTemplate:
<DataTemplate DataType="{x:Type local:DataSource}">
<TextBox Style="{StaticResource TextBoxStyle}" Text="{Binding Path=myData}" />
</DataTemplate>
Is there now any way to dynamically style this element? (eg. change the background color in some parts of the application) My problems are:
if you set a style in some parent-control, it gets ignored since there is already a style in the datatemplate
if you set a property on a parent-control, it doesn't get inherited since styles have precedence over property inheritence
Does anyone see a way to do this?
you can try it using DynamicResource
<DataTemplate DataType="{x:Type local:DataSource}">
<TextBox Style="{DynamicResource TextBoxStyle}" Text="{Binding Path=myData}" />
</DataTemplate>
and then later if you need to change the style in any other control. you can just declare same resource with same key for that control. Suppose you are using it in ListBox.
<ListBox>
<ListBox.Resources>
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<!--define changed style.-->
</Style>
</ListBox.Resources>
</ListBox>
new style will apply over there.

Get context in which a view is rendered?

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>

Enable a TabItem via Binding

I want to use MVVM in an application where the different pages are TabItems.
For this I use an observable collection of my view models (Items) and bind it to the tabcontrols ItemSource.
For each view model, I created an individual data template to render the correct view like this:
<DataTemplate DataType="{x:Type baseVm:AViewModel}">
<baseVw:AView />
</DataTemplate>
To display the correct name in the tab's header I created another data template to be applied to each of the tab control's elements:
<DataTemplate x:Key="ViewModelTabTemplate">
<DockPanel>
<ContentPresenter Content="{Binding Path=Name}"/>
</DockPanel>
</DataTemplate>
The tab control looks like this:
<TabControl x:Name="myTabControl"
ItemsSource="{Binding Items}"
ItemTemplate="{DynamicResource ViewModelTabTemplate}">
</TabControl>
What I want to do now is to enable/disable tabs from within the view model that contains the collection. The view model's base class contains a dependency property IsEnabled and I would like to bind this to the views. If I do this directly in the view like this:
IsEnabled="{Binding IsEnabled, FallbackValue=true}"
the tab page's content gets disabled when I turn the IsEnabled property to false. But what I really want is to also disable the tabpage's tab and not just the content.
Thanks for any help!
Maybe you could try something like this -
<TabControl>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>

WPF TabControl and DataTemplates

I've got a set of ViewModels that I'm binding to the ItemsSource property of a TabControl. Let's call those ViewModels AViewModel, BViewModel, and CViewModel. Each one of those needs to have a different ItemTemplate (for the header; because they each need to show a different icon) and a different ContentTemplate (because they have very different interaction models).
What I'd like is something like this:
Defined in Resource.xaml files somewhere:
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type CViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type CViewModel}">
...
</DataTemplate>
Defined separately:
<TabControl ItemTemplate="[ Some way to select "ItemTemplate" based on the type ]"
ContentTemplate="[ Some way to select "ContentTemplate" based on the type ]"/>
Now, I know that realistically, each time I define a DataTemplate with the same key the system is just going to complain. But, is there something I can do that's similar to this that will let me put a DataTemplate into a TabControl based on a name and a DataType?
The easiest way would be to use the automatic template system, by including the DataTemplates in the resources of a ContentControl. The scope of the templates are limited to the element they reside within!
<TabControl ItemsSource="{Binding TabViewModels}">
<TabControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type CViewModel}">
...
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<DataTemplate DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type CViewModel}">
...
</DataTemplate>
</TabControl.Resources>
</TabControl>
You can remove the x:Key :) This will automatically apply the template when the given type is encountered (probably one of the most powerful and underused features of WPF, imo.
This Dr. WPF article goes over DataTemplates pretty well. The section you'll want to pay attention to is "Defining a Default Template for a Given CLR Data Type".
http://www.drwpf.com/blog/Home/tabid/36/EntryID/24/Default.aspx
If this doesn't help your situation, you might be able to do something close to what you are looking for using a Style (ItemContainerStyle) and setting the content and header based on the type using a data trigger.
The sample below hinges on your ViewModel having a property called "Type" defined pretty much like this (easily put in a base ViewModel if you have one):
public Type Type
{
get { return this.GetType(); }
}
So as long as you have that, this should allow you to do anything you want. Note I have "A Header!" in a textblock here, but that could easily be anything (icon, etc).
I've got it in here two ways... one style applies templates (if you have a significant investment in these already) and the other just uses setters to move the content to the right places.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
xmlns:local="clr-namespace:WpfApplication1">
<Window.Resources>
<CompositeCollection x:Key="MyCollection">
<local:AViewModel Header="A Viewmodel" Content="A Content" />
<local:BViewModel Header="B ViewModel" Content="B Content" />
</CompositeCollection>
<DataTemplate x:Key="ATypeHeader" DataType="{x:Type local:AViewModel}">
<WrapPanel>
<TextBlock>A Header!</TextBlock>
<TextBlock Text="{Binding Header}" />
</WrapPanel>
</DataTemplate>
<DataTemplate x:Key="ATypeContent" DataType="{x:Type local:AViewModel}">
<StackPanel>
<TextBlock>Begin "A" Content</TextBlock>
<TextBlock Text="{Binding Content}" />
</StackPanel>
</DataTemplate>
<Style x:Key="TabItemStyle" TargetType="TabItem">
<Style.Triggers>
<!-- Template Application Approach-->
<DataTrigger Binding="{Binding Path=Type}" Value="{x:Type local:AViewModel}">
<Setter Property="HeaderTemplate" Value="{StaticResource ATypeHeader}" />
<Setter Property="ContentTemplate" Value="{StaticResource ATypeContent}" />
</DataTrigger>
<!-- Just Use Setters Approach -->
<DataTrigger Binding="{Binding Path=Type}" Value="{x:Type local:BViewModel}">
<Setter Property="Header">
<Setter.Value>
<WrapPanel>
<TextBlock Text="B Header!"></TextBlock>
<TextBlock Text="{Binding Header}" />
</WrapPanel>
</Setter.Value>
</Setter>
<Setter Property="Content" Value="{Binding Content}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TabControl ItemContainerStyle="{StaticResource TabItemStyle}" ItemsSource="{StaticResource MyCollection}" />
</Grid>
HTH, Anderson
One way would be to use DataTemplateSelectors and have each one resolve the resource from a separate ResourceDictionary.
In this example I use DataTemplates in the resources section of my TabControl for each view model I want to display in the tab items.
In this case I map ViewModelType1 to View1 and ViewModelType2 to View2.
The view models will be set as DataContext object of the views automatically.
For displaying the tab item header, I use an ItemTemplate.
The view models I bind to are of different types, but derive from a common base class ChildViewModel that has a Title property. So I can set up a binding to pick up the title to display it in the tab item header.
In addition I display a "Close" Button in the tab item header. If you do not need that, just remove the button from the example code so you just have the header text.
The contents of the tab items are rendered with a simple ItemTemplate which displays the view in a content control with Content="{Binding}".
<UserControl ...>
<UserControl.DataContext>
<ContainerViewModel></ContainerViewModel>
</UserControl.DataContext>
<TabControl ItemsSource="{Binding ViewModels}"
SelectedItem="{Binding SelectedViewModel}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type ViewModelType1}">
<View1/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModelType2}">
<View2/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Title}" />
<Button DockPanel.Dock="Right" Margin="5,0,0,0"
Visibility="{Binding RemoveButtonVisibility}"
Command="{Binding DataContext.CloseItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TypeOfContainingView}}}"
>
<Image Source="/Common/Images/ActiveClose.gif"></Image>
</Button>
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</UserControl>
The user control which contains the tab control has a container view model of type ContainerViewModel as DataContext. Here I have a collection of all the view models displayed in the tab control. I also have a property for the currently selected view model (tab item).
This is a shortened version of my container view model (I skipped the change notification part).
public class ContainerViewModel
{
/// <summary>
/// The child view models.
/// </summary>
public ObservableCollection<ChildViewModel> ViewModels {get; set;}
/// <summary>
/// The currently selected child view model.
/// </summary>
public ChildViewModel SelectedViewModel {get; set;}
}
Josh Smith uses exactly this technique (of driving a tab control with a view model collection) in his excellent article and sample project WPF Apps With The Model-View-ViewModel Design Pattern. In this approach, because each item in the VM collection has a corresponding DataTemplate linking the View to the VM Type (by omitting the x:Key as Anderson Imes correctly notes), each tab can have a completely different UI. See the full article and source code for details.
The key parts of the XAML are:
<DataTemplate DataType="{x:Type vm:CustomerViewModel}">
<vw:CustomerView />
</DataTemplate>
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
There is one downside - driving a WPF TabControl from an ItemsSource has performance issues if the UI in the tabs is big/complex and therefore slow to draw (e.g., datagrids with lots of data). For more on this issue, search SO for "WPF VirtualizingStackPanel for increased performance".

Resources