I have created a somewhat complex shape using basic shapes. I would like to use multiples of this on a canvas, created programmatically in code behind.
I have thought about creating a UserControl, but what's the best way to encapsulate this composite shape?
For most purposes putting them in a ControlTemplate or DataTemplate works the best. Here's the ControlTemplate way:
<ResourceDictionary>
<ControlTemplate x:Key="MyShape">
<Grid With="..." Height="...">
<Rectangle ... />
<Ellipse ... />
<Path ... />
</Grid>
</ControlTemplate>
</ResourceDictionary>
...
<Canvas ...>
<Control Template="{StaticResource MyShape}" ... />
<Control Template="{StaticResource MyShape}" ... />
<Control Template="{StaticResource MyShape}" ... />
<Control Template="{StaticResource MyShape}" ... />
</Canvas>
And the DataTemplate way:
<ResourceDictionary>
<DataTemplate x:Key="MyShape">
<Grid With="..." Height="...">
<Rectangle ... />
<Ellipse ... />
<Path ... />
</Grid>
</DataTemplate>
</ResourceDictionary>
...
<Canvas ...>
<ContentPresenter ContentTemplate="{StaticResource MyShape}" ... />
<ContentPresenter ContentTemplate="{StaticResource MyShape}" ... />
<ContentPresenter ContentTemplate="{StaticResource MyShape}" ... />
<ContentPresenter ContentTemplate="{StaticResource MyShape}" ... />
</Canvas>
To choose between these, decide what additional functionality (if any) you want. You will probably want to add properties to your control or your data object.
If you go with a ControlTemplate, your custom control can participate in property inheritance and be part of the visual tree, receiving all the events. You can also refer to both the DataContext and the TemplatedParent in the bindings, which is more flexible.
If you go with a DataTemplate, you can work directly against objects in your model.
Instead of listing individual controls you can also use ItemsControl and its subclasses (ListBox, ComboBox, etc) to present your shapes appropriately.
Alternate approach
Another completely different approach is to convert your collection of shapes to a Drawing object and present it using a DrawingImage or a DrawingBrush.
Related
I am trying to customize a DataGrid. I want to insert some content above the column headers. I am trying to use the ControlTemplate to do this. I have my XAML code below. My problem is that the <ContentPresenter /> is not outputting anything. When I load the page, the after TextBlock appears directly below the before TextBlock with nothing in between. I want to display the column headers in that space.
<DataGrid ItemsSource="{Binding List}" AutoGenerateColumns="True">
<DataGrid.Template>
<ControlTemplate>
<StackPanel Orientation="Vertical">
<TextBlock>before</TextBlock>
<ContentPresenter /> <!-- outputs nothing -->
<TextBlock>after</TextBlock>
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</DataGrid.Template>
</DataGrid>
How do I display the column headers between the before TextBlock and the after TextBlock? My List object is simply a BindingList of some generic class that has a couple of public properties.
I found the answer. I should use <DataGridColumnHeadersPresenter /> instead of <ContentPresenter />. So my code that works looks like:
<DataGrid ItemsSource="{Binding List}" AutoGenerateColumns="True">
<DataGrid.Template>
<ControlTemplate>
<StackPanel Orientation="Vertical">
<TextBlock>before</TextBlock>
<DataGridColumnHeadersPresenter />
<TextBlock>after</TextBlock>
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</DataGrid.Template>
</DataGrid>
I'm creating a custom control and I'm trying to create partially specified template for list box items. The template has some predefined parts and there should be another part that can be templated when using the control.
For this I have created a dependency property named SuggestionItemTemplate like so:
public static readonly DependencyProperty SuggestionItemTemplateProperty =
DependencyProperty.Register("SuggestionItemTemplate",
typeof(DataTemplate),
typeof(AutoSuggestTextBox),
new PropertyMetadata(null));
In my custom controls' generic.xaml I have:
<Style TargetType="local:AutoSuggestTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:AutoSuggestTextBox">
<Grid>
<ListBox x:Name="ItemsControl">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
ContentTemplate="{TemplateBinding SuggestionItemTemplate}"
Content="{Binding}" />
<ToggleButton Grid.Column="1"
x:Name="DetailsHover"
ClickMode="Hover"
Style="{StaticResource DetailsToggleButtonStyle}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Unfortunatelly, this does not work as it's not possible to use TemplateBinding from inside ContentPresenter nested into DataTemplate. (The member "SuggestionItemTemplate" is not recognized or is not accessible.)
I also tried to use ancestor binding (available in Silverlight 5) like:
<ContentPresenter Grid.Column="0"
ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:AutoSuggestTextBox}, Path=SuggestionItemTemplate}"
Content="{Binding}" />
But this results in binding error:
Error: System.Exception: BindingExpression_CannotFindAncestor
I suppose this happens because I'm inside ControlTemplate of my custom control and "local:AutoSuggestTextBox" is not defined anywhere in the style.
The third option that I tried was to apply ContentTemplate in OnApplyTemplate override but this also doesn't work:
var cp = itemsControlElement.ItemTemplate.LoadContent() as ContentPresenter;
cp.ContentTemplate = SuggestionItemTemplate;
In all cases, I get my grid with two columns, toggle button is visible but content presenter simple prints out view model's type name. (I believe this is the default behavior if the ContentTemplate is null).
Is this even possible to do? Are there any other ways to specify a partial template and then only add customized template part when necessary?
As a workaround for now, I can specify
ItemTemplate="{TemplateBinding SuggestionItemTemplate}"
for the list box and then copy/paste the generic template everywhere I use this control. But this is the behavior I'm hoping to avoid in the first place.
Thanks!
edit: I used the code tags for all blocks of code, but they're not highlighted for some reason. :/
It is possible to walk through Visual Ancestors in the OnApplyTemplate method, find your ContentPresenter(s) and set the ItemTemplate on that. To my mind, this is fine for a single item, but not so much in an ItemsControl scenario.
You could achieve what you are after using your own custom Control. Just give it a Content dependency property of type Object, and a Template DP of type DataTemplate (and multiples of the two if you fancy), and you can set up the root visual style and templates in the default style for your Control.
In this specific case, I would suggest that the best approach is to put your ToggleButton in the ListBoxItem template instead by customising the ListBox.ItemContainerStyle. It is easy to modify the default Control Template using Expression Blend, and the DataContext of the ToggleButton will not change, so the changes to your own logic should be minimal.
Edit: If you mean to use a number of different data templates, perhaps Implicit Data Templates will be more suitable.
I managed to solve this using a different approach. I used ancestor binding but instead of trying to reach the root control (my AutoSuggestTextBox) from the DataTemplate, I ask for a reference to my ListBox (here named ItemsControl).
However, since the ListBox doesn't have the SuggestionItemTemplate property, I sub-classed it to my own CustomListBox where I implemented that property. It all comes down to this code snippet:
<Style TargetType="local:AutoSuggestTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:AutoSuggestTextBox">
<Grid>
<local:CustomizableListBox x:Name="ItemsControl"
SuggestionItemTemplate="{TemplateBinding SuggestionItemTemplate}">
<local:CustomizableListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:CustomizableListBox}, Path=SuggestionItemTemplate}"
Content="{Binding}" />
<ToggleButton Grid.Column="1"
x:Name="DetailsHover"
ClickMode="Hover"
Style="{StaticResource DetailsToggleButtonStyle}" />
</Grid>
</DataTemplate>
</local:CustomizableListBox.ItemTemplate>
</local:CustomizableListBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Got a view with lots of objects inside, which get their View from a DataTemplate declaration:
<DataTemplate DataType="{x:Type vm:StatusAViewModel}" >
<vw:StatusAView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:StatusBViewModel}" >
<vw:StatusBView />
</DataTemplate>
Now I want to show a popup with its content based on the type of the data it's to contain:
<Popup AllowsTransparency="True"
IsOpen="{Binding IsPopupOpen,Mode=OneWay}"
PlacementTarget="{Binding PopupPlacementElement}" Placement="{Binding PopupPlacementMode}"
HorizontalOffset="{Binding PopupHOffset}" VerticalOffset="{Binding PopupVOffset}">
<ContentPresenter x:Name="StuckHere" Content="{Binding PopupData}" />
</Popup>
A ContentTemplateSelector on StuckHere doesn't work because it's only evaluated once and when the PopupData changes, the template isn't re-selected.
All the examples I can find rely on a default datatemplate, which I can't use in my case because I've already got a default DataTemplate for the main view, I only want this different template to affect my popup.
Any clues?
You can apply a different set of DataTemplates in <Popup.Resources>, which will override the ones defined higher up in the visual tree and apply to that Popup only
<Window.Resources>
<DataTemplate DataType="{x:Type vm:StatusAViewModel}" >
<vw:StatusAView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:StatusBViewModel}" >
<vw:StatusBView />
</DataTemplate>
</Window.Resources>
<Popup>
<Popup.Resources>
<DataTemplate DataType="{x:Type vm:StatusAViewModel}" >
<vw:StatusAPopupView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:StatusBViewModel}" >
<vw:StatusBPopupView />
</DataTemplate>
</Popup.Resources>
<!-- The DataTeplate used here will be a PopupView, not the regular View -->
<ContentPresenter Content="{Binding PopupData}" />
</Popup>
You may take a look at http://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector.aspx. Implement a templateselector derived from DateTemplateSelector and use a ContentControl. Bind the Content to your DataObject and the ContentTemplateSelectorTemplate to your TemplateSelector.
I have a Canvas in a ResourceDictionary xaml file like so:
<Canvas x:Key="Icon">
<Path ... />
<Path ... />
</Canvas>
In my code-behind I load this icon using
LayoutGrid.Children.Add(FindResource("Icon") as Canvas);
And this works fine. Now I want to create a button that uses the same icon as a template. So I create a control template:
<ControlTemplate x:Key="IconTemplate">
...
</ControlTemplate>
Now here's the problem: How would I put the "Icon" resource canvas into the control template? As far as I know, Canvas does not have a Style or Template property. It has a Children property, but it's not accessible via XAML. How would I go about using my canvas in a template?
When you create a type such as canvas as a resource, then you are creating ONE instance of the type. This means that you cannot place that resource in more than one location in your application (an element can only be in one place at a time). You should look at using control templates, I think.
You don't need any code behind for this.
Something like this:
<ControlTemplate x:Key="Icon">
<Canvas>
<Path ... />
<Path ... />
</Canvas>
</ControlTemplate>
Then elsewhere you do something like this:
<Button>
<Control Template="{StaticResource Icon}" />
</Button>
This constructs a regular looking button with your icon as it's content. The content of that button is your icon.
If, however, you want to completely redefine the template of your button, then you would do so
<ControlTemplate x:Key="Icon" TargetType="Button">
<Canvas>
<Path ... />
<Path ... />
</Canvas>
</ControlTemplate>
Then elsewhere you do something like this:
<Button Template="{StaticResource Icon}" />
Note that this isn't a great style for a button. Look at this example from Microsoft for an example of a more fully featured button template.
EDIT
Unless you have a ContentPresenter in your ControlTemplate, then there's no need to assign the template to a content control. Note that any class derived from Control can be templated, including Control itself. So in order to place an item into your view, then you can just use:
<Control Template="{StaticResource Icon}" />
This uses the widest applicable type in the hierarchy, which is also the lightest.
A good way to do define an icon for a button is to use a DrawingBrush and set it as the fill of a Rectangle that you embed in the Button:
<Button>
<Rectangle
Width="32"
Height="32"
Fill={Background myDrawingBrush}
/>
</Button>
myDrawingBrush must be defined in resources like this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options">
<DrawingBrush x:Key="dialogerror" Stretch="Uniform">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing>
... define geometry here ...
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</ResourceDictionary>
What is difference between a ControlTemplate and a DataTemplate in WPF?
Typically a control is rendered for its own sake, and doesn't reflect underlying data. For example, a Button wouldn't be bound to a business object - it's there purely so it can be clicked on. A ContentControl or ListBox, however, generally appear so that they can present data for the user.
A DataTemplate, therefore, is used to provide visual structure for underlying data, while a ControlTemplate has nothing to do with underlying data and simply provides visual layout for the control itself.
A ControlTemplate will generally only contain TemplateBinding expressions, binding back to the properties on the control itself, while a DataTemplate will contain standard Binding expressions, binding to the properties of its DataContext (the business/domain object or view model).
Very basically a ControlTemplate describes how to display a Control while a DataTemplate describes how to display Data.
For example:
A Label is a control and will include a ControlTemplate which says the Label should be displayed using a Border around some Content (a DataTemplate or another Control).
A Customer class is Data and will be displayed using a DataTemplate which could say to display the Customer type as a StackPanel containing two TextBlocks one showing the Name and the other displaying the phone number. It might be helpful to note that all classes are displayed using DataTemplates, you will just usually use the default template which is a TextBlock with the Text property set to the result of the Object's ToString method.
Troels Larsen has a good explanation on MSDN forum
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="ButtonContentTemplate">
<StackPanel Orientation="Horizontal">
<Grid Height="8" Width="8">
<Path HorizontalAlignment="Stretch"
Margin="0,0,1.8,1.8"
VerticalAlignment="Stretch" Stretch="Fill" Stroke="#FF000000"
Data="M0.5,5.7 L0.5,0.5 L5.7,0.5"/>
<Path HorizontalAlignment="Stretch"
Margin="2,3,0,0"
VerticalAlignment="Stretch" Stretch="Fill" Stroke="#FFFFFFFF"
Data="M3.2,7.5 L7.5,7.5 L7.5,3.5"/>
<Path HorizontalAlignment="Stretch"
Margin="1.2,1.4,0.7,0.7"
VerticalAlignment="Stretch" Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FF000000"
Data="M2.5,2.5 L7.5,7.5"/>
<Path HorizontalAlignment="Stretch"
Margin="1.7,2.0,1,1"
VerticalAlignment="Stretch" Stretch="Fill" Stroke="#FF000000"
Data="M3,7.5 L7.5,7.5 L7.5,3.5"/>
<Path HorizontalAlignment="Stretch"
Margin="1,1,1,1"
VerticalAlignment="Stretch" Stretch="Fill" Stroke="#FFFFFFFF"
Data="M1.5,6.5 L1.5,1 L6.5,1.5"/>
</Grid>
<ContentPresenter Content="{Binding}"/>
</StackPanel>
</DataTemplate>
<ControlTemplate TargetType="Button" x:Key="ButtonControlTemplate">
<Grid>
<Ellipse Fill="{TemplateBinding Background}"/>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Window.Resources>
<StackPanel>
<Button Template="{StaticResource ButtonControlTemplate}" ContentTemplate="{StaticResource ButtonContentTemplate}" Content="1"/>
<Button Template="{StaticResource ButtonControlTemplate}" ContentTemplate="{StaticResource ButtonContentTemplate}" Content="2"/>
<Button Template="{StaticResource ButtonControlTemplate}" ContentTemplate="{StaticResource ButtonContentTemplate}" Content="3"/>
</StackPanel>
</Window>
(Templates blatently stolen from
http://msdn.microsoft.com/en-us/library/system.windows.controls.controltemplate.aspx
and
http://msdn.microsoft.com/en-us/library/system.windows.controls.contentcontrol.contenttemplate%28VS.95%29.aspx
respectively)
Anyway, the ControlTemplate decides how the Button itself looks, while
the ContentTemplate decides how the Content of the button looks. So
you could bind the content to one of you data classes and have it
present itself however you wanted it.
ControlTemplate: Represents control style.
DataTemplate: Represents data style(How would you like to show your data).
All controls are using default control template that you can override through template property.
For example
Button template is a control template.
Button content template is a data template
<Button VerticalAlignment="Top" >
<Button.Template>
<ControlTemplate >
<Grid>
<Rectangle Fill="Blue" RadiusX="20" RadiusY="20"/>
<Ellipse Fill="Red" />
<ContentPresenter Content="{Binding}">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="50">
<TextBlock Text="Name" Margin="5"/>
<TextBox Text="{Binding UserName, Mode=TwoWay}" Margin="5" Width="100"/>
<Button Content="Show Name" Click="OnClickShowName" />
</StackPanel>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
public String UserName
{
get { return userName; }
set
{
userName = value;
this.NotifyPropertyChanged("UserName");
}
}
ControlTemplate - Changing the appearance of element. For example Button can contain image and text
DataTemplate - Representing the underlying data using the elements.
ControlTemplate DEFINES the visual appearance, DataTemplate REPLACES the visual appearance of a data item.
Example: I want to show a button from rectangular to circle form => Control Template.
And if you have complex objects to the control, it just calls and shows ToString(), with DataTemplate you can get various members and display and change their values of the data object.
All of the above answers are great but there is a key difference that was missed. That helps make better decisions about when to use what. It is ItemTemplate property:
DataTemplate is used for elements that provide ItemTemplate property for you to replace its items' content using DataTemplates you define previously according to bound data through a selector that you provide.
But if your control does not provide this luxury for you then you still can use a ContentView that can display its content from predefined ControlTemplate. Interestingly, you can change the ControlTemplate property of your ContentView at runtime. One more thing to note that unlike controls with ItemTemplate property, you cannot have a TemplateSelector for this (ContentView) control. However, you still can create triggers to change the ControlTemplate at runtime.