I want to create multiple groups of control(s) within a rectangular border. where each group will be containing control within it, surrounded by rectangular border and a header (optional) is to be placed over each child group's top-left above its border.
So, I created a class GroupLayout, each child element within this have to create its own new group. I created Header as an attached property.
Syntax making use of template is as:-
<GroupLayout Orientation = "Vertical">
<DataGrid GroupLayout.Header= "Group 1" />
<Grid GroupLayout.Header= "Group 2" />
-------So On--------
</GroupLayout>
as above given, DataGrid and Grid both should form there own two groups with vertical orientation. each child element should create its own new group.
So, I tried this as User Control:-
<Style TargetType = "GroupLayout">
<Setter.Property>
<ControlTemplate TargetType="GroupLayout">
<StackPanel>
<Border x:Name="MainParentGroupBorder">
<StackPanel>
<ContentPresenter Content = "{TemplateBinding HeaderLabel}" />
<Border x:Name="ChildGroupBorder">
<ContentPresenter Content = "{TemplateBinding Content}" />
</Border>
</StackPanel>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Property>
</Style>
In code behind I'm driving from ItemsControl.
But, this is not working as required. Now after a lot of efforts, I think I have to implement ItemTemplate in Xaml here. but I'm not able to do so to get the required result. Please help me.
Thanks,
GK Prajapati
It looks to me like you're re-inventing the wheel.
each group will be containing control within it, surrounded by rectangular border and a header (optional) is to be placed over each child group's top-left above its border
This is perfectly covered by an existing control: HeaderedContentControl. All you have to do is provide an appropriate control template for it. I suggest something like this:
<ItemsControl>
<controls:HeaderedContentControl Header="Group 1">
<DataGrid />
</controls:HeaderedContentControl>
<controls:HeaderedContentControl Header="Group 2">
<Grid />
</controls:HeaderedContentControl>
</ItemsControl>
Now, give the HeaderedContentControl the appropriate template:
<Style TargetType="controls:HeaderedContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:HeaderedContentControl">
<StackPanel>
<Border x:Name="MainParentGroupBorder">
<StackPanel>
<ContentPresenter Content="{TemplateBinding Header}" />
<Border x:Name="ChildGroupBorder">
<ContentPresenter Content="{TemplateBinding Content}" />
</Border>
</StackPanel>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Edit
Expanding on the above -- if you need the specific syntax of using an attached property GroupLayout.Header, then I suggest having your GroupLayout class override the ItemsControl.GetContainerForItem method. Have it return an instance of HeaderedContentControl:
protected override DependencyObject GetContainerForItemOverride()
{
return new HeaderedContentControl();
}
Now, you can use another ItemsControl override -- PrepareContainerForItemOverride -- to pass along your attached property:
protected virtual void PrepareContainerForItemOverride(DependencyObject element, Object item)
{
// get the attached property from the ItemsControl item
string header = ((FrameworkElement)item).GetValue(GroupLayout.Header) as string;
// set the container's "Header"
((HeaderedContentControl)element).Header = header;
}
Now you can use the exact XAML syntax you need:
<GroupLayout Orientation = "Vertical">
<DataGrid GroupLayout.Header= "Group 1" />
<Grid GroupLayout.Header= "Group 2" />
</GroupLayout>
Related
I need to lock the Z-order of a canvas/content control after it is dragged by a Thumb.
In the below image, the "Joe Smith" pops above the others the other two ellipses while the the mouse over is active. Once the drag stops and the mouse moves out, it drops back to its value.
I am unable to find a solution within the design I have shown below to keep it locked above the others.
Minimal Reproducible Example
I have created a code gist of all code that contains the xaml, the thumb class and the people class. All one has to do is create a .Net Core WPF app and drop in the snippets of code and set name spacing in Xaml as needed.
Design
There is an ItemsControl which has DataTemplate defined with a Canvas. Each content in the ItemsControl has ContentControl which has a Thumb as applied by a style.
Another style trigger of the mouse entering the ContentPresenter has the content temporarily pushed in zIndex above the others by setting the content's internal Grid's zIndex to 1.
How to stick that zIndex?
Xaml
<ItemsControl Margin="10" ItemsSource="{Binding Source={StaticResource People}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<ContentControl Width="100" Height="100" >
<Grid>
<Ellipse Fill="Silver">
<Ellipse.Effect>
<DropShadowEffect Color="Black" Direction="320" ShadowDepth="6" Opacity="0.5"/>
</Ellipse.Effect>
</Ellipse>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Margin="3,3,3,0" Text="{Binding Path=First}"/>
<TextBlock Margin="3,0,3,7" Text="{Binding Path=Last}"/>
</StackPanel>
</Grid>
</ContentControl>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Grid.ZIndex" Value="1"/>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
See the Gist for all supporting classes and styles to reproduce
Actual Design
The ultimate goal is to have a panel of images of a set size, when the user grabs the thumb the image it will move forward and lock above the others. I say this in-case there is another way to do that which could provide an answer above the minimal example design.
In my ItemsControl I changed the ItemsPanelTemplate to be a Canvas such as
<ItemsPanelTemplate>
<Canvas x:Name="MainCanvas" />
</ItemsPanelTemplate>
Which when looking at the Visual Tree where the user was clicking the ContentControl, it had a parent of a Canvas that had a ContentPresenter with that top level Canvas such as (see named MainCanvas):
I changed the ContentControl to have a MouseEnter event :
<ContentControl Width="100" Height="100" MouseEnter="EnterMouse">
<Grid>
<Ellipse Fill="Silver">
In that method I needed to find the named "MainCanvas" Canvas and enumerate all of its children the ContentPresenters and extract the max ZIndex and then set my ContentControl (shown in blue above) to that ZIndex value plus 1.
Here is the code behind where extract the necessary the parents:
private void EnterMouse(object sender, MouseEventArgs e)
{
if (sender is ContentControl cc)
{
var cpParent = FindParent<ContentPresenter>(cc);
var p2 = FindParent<Canvas>(cpParent);
var max = p2.Children.Cast<UIElement>().Max(control => Panel.GetZIndex(control));
Panel.SetZIndex(cpParent, max + 1);
}
}
private T FindParent<T>(DependencyObject child) where T : DependencyObject
{
DependencyObject immediateParent = VisualTreeHelper.GetParent(child);
T parent = immediateParent as T;
return parent ?? FindParent<T>(immediateParent);
}
So I realized that my graph viewer had the axis displaying over the actual items in the graph, so I changed the ZIndex on the grid to display the items over the axis instead.
However, I noticed that I couldn't actually see anything under the actual items because the background of the items were opaque. I think I have two options then, to either set the background of the items to transparent, or to set the opacity of the items. Is there any difference between these two options?
<Grid
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Grid.ZIndex="1"
>
<Components:SignalGraphAxis
x:Name="signal_axis"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
GraphHeight="{Binding Path=GraphHeight}"
PenColor="{Binding Path=AxisColor, Mode=OneWay}"
PenWidth="{Binding Path=GraphPenWidth, Mode=OneWay}"
MinHeight="10"
MinWidth="10"
AxisTimeScale="{Binding Path=GraphTimeScale}"
NumberOfPixelsPerDivision="{Binding Path=NumberOfPixelsPerDivision, Mode=OneWay}"
MinDisplayValue ="{Binding Path=MinDisplayValue, Mode=OneWay}"
UnitsOfGraphTimePerInch="{Binding Path=UnitsOfTimePerInch, Mode=OneWay}"
/>
</Grid>
<ScrollViewer
x:Name="signal_scrollviewer"
Grid.Row="1"
Grid.RowSpan="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.ZIndex="2"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
CanContentScroll="True"
Style="{StaticResource SignalScrollViewerStyle}"
>
<ItemsPresenter />
</ScrollViewer>
</Grid>
Background property is defined on Control class and Opacity is defined much higher on UIElement.
From MSDN Page Control.Background Property
This property only affects a control whose template uses the
Background property as a parameter. On other controls, this property
has no impact.
Let's try to create a Custom Control to see how this works.
CustomControl1.cs
public class CustomControl1 : ContentControl
{
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
}
Default Template For CustomControl1
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="My Custom Control " Grid.Row="0" />
<ContentPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Please note, the above template doesn't use Background property at all in it's Template.
Now, Let's try to use that in a Form and see how it behaves:
Code from Window1.xaml
<Grid>
<wpfApplication5:CustomControl1 Background="Green">
<Button Content="Button Within Custom Control" Margin="25"/>
</wpfApplication5:CustomControl1>
</Grid>
The resultant output:
See, there was no Green background for the rendered CustomControl even though we set the Background to Green in Window1.xaml.
Now, Lets modify the template to use Background property.
Template with Background Property
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Grid Background="{TemplateBinding Background}">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="My Custom Control " Grid.Row="0" />
<ContentPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And the resultant output will now have a the specified background applied.
I think (couldn't find any references though), Opacity gets applied to the element/Control whether the Control's Template worry about Opacity property or not.
Window1.xam with Opacity Set on CustomControl
<Grid>
<wpfApplication5:CustomControl1 Background="Green" Opacity="0.2">
<Button Content="Button Within Custom Control" Margin="25"/>
</wpfApplication5:CustomControl1>
</Grid>
and resultant Output
See, the Opacity got applied even though our Custom Control's template doesn't worry anything about Opacity property.
Finally, to answer your question: Though either setting Opacity to 0 or Background to Transparent may give you the same visual result. But, for Background property, it totally depends on the Control implementation and how it handles Background property. Whereas, with Opacity it gets applied from parent elements to down the elements tree to child elements irrespective of the control.
Refer to MSDN Page, UIElement.Opacity Property to read more on Opacity property and how it behaves when Opacity is set at multiple levels in an element tree.
What is the difference between
ControlTemplate
DataTemplate
HierarchalDataTemplate
ItemTemplate
Control Template
A ControlTemplate specifies the visual structure and visual behavior of a control. You can customize the appearance of a control by giving it a new ControlTemplate. When you create a ControlTemplate, you replace the appearance of an existing control without changing its functionality. For example, you can make the buttons in your application round rather than the default square shape, but the button will still raise the Click event.
An Example of ControlTemplate would be
Creating a Button
<Button Style="{StaticResource newTemplate}"
Background="Navy"
Foreground="White"
FontSize="14"
Content="Button1"/>
ControlTemplate for Button
<Style TargetType="Button" x:Key="newTemplate">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="RootElement">
<!--Create the SolidColorBrush for the Background
as an object elemment and give it a name so
it can be referred to elsewhere in the control template.-->
<Border.Background>
<SolidColorBrush x:Name="BorderBrush" Color="Black"/>
</Border.Background>
<!--Create a border that has a different color by adding smaller grid.
The background of this grid is specificied by the button's Background
property.-->
<Grid Margin="4" Background="{TemplateBinding Background}">
<!--Use a ContentPresenter to display the Content of
the Button.-->
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="4,5,4,4" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
More about ControlTemplate
Data Templates
Data Template are a similar concept as Control Templates. They give you a very flexible and powerful solution to replace the visual appearance of a data item in a control like ListBox, ComboBox or ListView. WPF controls have built-in functionality to support the customization of data presentation.
An Example for the DataTemplate would be
<!-- Without DataTemplate -->
<ListBox ItemsSource="{Binding}" />
<!-- With DataTemplate -->
<ListBox ItemsSource="{Binding}" BorderBrush="Transparent"
Grid.IsSharedSizeScope="True"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Key" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBox Grid.Column="1" Text="{Binding Value }" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
More about DataTemplates and Triggers
Item Templates
You use the ItemTemplate to specify the visualization of the data objects. If your ItemsControl is bound to a collection object and you do not provide specific display instructions using a DataTemplate, the resulting UI of each item is a string representation of each object in the underlying collection.
An Example for Item Template would be
<ListBox Margin="10" Name="lvDataBinding">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Name: " />
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text=", " />
<TextBlock Text="Age: " />
<TextBlock Text="{Binding Age}" FontWeight="Bold" />
<TextBlock Text=" (" />
<TextBlock Text="{Binding Mail}" TextDecorations="Underline" Foreground="Blue" Cursor="Hand" />
<TextBlock Text=")" />
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When you set an ItemTemplate on an ItemsControl, the UI is generated as follows (using the ListBox as an example):
During content generation, the ItemsPanel initiates a request for the ItemContainerGenerator to create a container for each data item. For ListBox, the container is a ListBoxItem. The generator calls back into the ItemsControl to prepare the container.
Part of the preparation involves the copying of the ItemTemplate of the ListBox to be the ContentTemplate of the ListBoxItem.
Similar to all ContentControl types, the ControlTemplate of a ListBoxItem contains a ContentPresenter. When the template is applied, it creates a ContentPresenter whose ContentTemplate is bound to the ContentTemplate of the ListBoxItem.
Finally, the ContentPresenter applies that ContentTemplate to itself, and that creates the UI.
If you have more than one DataTemplate defined and you want to supply logic to programmatically choose and apply a DataTemplate, use the ItemTemplateSelector property.
The ItemsControl provides great flexibility for visual customization and provides many styling and templating properties. Use the ItemContainerStyle property or the ItemContainerStyleSelector property to set a style to affect the appearance of the elements that contain the data items. For example, for ListBox, the generated containers are ListBoxItem controls; for ComboBox, they are ComboBoxItem controls. To affect the layout of the items, use the ItemsPanel property. If you are using grouping on your control, you can use the GroupStyle or GroupStyleSelector property.
For more information, see Data Templating Overview.
ControlTemplaes defines the "look" and the "behavour" of a control. A button is rectangular by default. A ListBox has a white background by default. These are all defineed by Control's ControlTemple.
A DataTemplae helps a Control with Layout of Data that it holds. If a list of Users are added to listbox and you would like UserName to show up before UserPassword then you will define this inside a DataTemples. DataTemples is assigned to the ItemTemplate (4) Property of the ListBox.
HierarchalDataTemplte is same as DataTemples except that it deal with Hierarchal Data Source. It is commonlly used with TreeView Control.
Is there some way to apply styles to the first (or last or nth) child of a container (anything that contains children)? I am trying to customize the look of tab items so that the first one has different border radius than the others.
This is what I have now:
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border Name="Border" BorderBrush="#666" BorderThickness="1,1,1,0" CornerRadius="8,8,0,0" Margin="0,0,0,-1">
<TextBlock x:Name="TabItemText" Foreground="#444" Padding="6 2" TextOptions.TextFormattingMode="Display">
<ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center" ContentSource="Header" Margin="12,2,12,2"/>
</TextBlock>
</Border>
</Grid>
</ControlTemplate>
For ItemsControl derived classes (such as TabControl), you can use the ItemContainerStyleSelector dependency property. When this dependency property is set, ItemsControl will call StyleSelector.SelectStyle() for each item in the control. This will allow you to use different styles for different items.
The following example changes the last tab item in a TabControl so its text is bold and a bit larger than the other tabs.
First, the new StyleSelector class:
class LastItemStyleSelector : StyleSelector
{
public override Style SelectStyle(object item, DependencyObject container)
{
var itemsControl = ItemsControl.ItemsControlFromItemContainer(container);
var index = itemsControl.ItemContainerGenerator.IndexFromContainer(container);
if (index == itemsControl.Items.Count - 1)
{
return (Style)itemsControl.FindResource("LastItemStyle");
}
return base.SelectStyle(item, container);
}
}
This style selector will return the style with the key "LastItemStyle" but only for the last item in the control. The other items will use the default style. (Note, that this function only uses members from ItemsControl. It could also be used for other ItemsControl derived classes.) Next, in your XAML, you first need to create two resources. The first resource will be to this LastItemStyleSelector and the second resource is the style.
<Window.Resources>
<local:LastItemStyleSelector x:Key="LastItemStyleSelector" />
<Style x:Key="LastItemStyle" TargetType="TabItem">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontSize" Value="16" />
</Style>
</Window.Resources>
Then finally your TabControl:
<TabControl ItemContainerStyleSelector="{StaticResource LastItemStyleSelector}">
<TabItem Header="First" />
<TabItem Header="Second" />
<TabItem Header="Third" />
</TabControl>
For more information see the MSDN documentation:
ItemsControl.ItemContainerStyleSelector Property
StyleSelector Class
Unlike HTML and CSS, there's not a simple way to determine and trigger that type of change.
You could potentially write a trigger and use a value converter to do something like that using this forum post as inspiration potentially.
Much simpler would be to apply a custom style to the tabitem that you want to look different. Have you tried that?
<TabItem Header="TabItem" Style="{DynamicResource FirstTabStyle}">
<Grid Background="#FFE5E5E5"/>
</TabItem>
I'm trying to have a custom control that requires 2 or more areas of the XAML to be defined by a child control - that inherits from this control. I'm wondering if there's a way to define multiple contentpresenters and one which acts as the default content presenter
<MyControl>
<MyControl.MyContentPresenter2>
<Button Content="I am inside the second content presenter!"/>
</MyControl.MyContentPresenter2>
<Button Content="I am inside default content presenter" />
</MyControl>
Is this possible, how do I define this in the custom control's template?
The template can just bind the separate ContentPresenter instances like this (I've only set one property here but you'll likely want to set others):
<ContentPresenter Content="{TemplateBinding Content1}"/>
<ContentPresenter Content="{TemplateBinding Content2}"/>
The control itself should expose two properties for content and set the default using the ContentPropertyAttribute:
[ContentProperty("Content1")]
public class MyControl : Control
{
// dependency properties for Content1 and Content2
// you might also want Content1Template, Content2Template, Content1TemplateSelector, Content2TemplateSelector
}
You can use an "ItemsControl" with a custom template.
<ItemsControl>
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Items[0]}"/>
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Items[1]}"/>
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Items[2]}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Style>
<TextBlock Text="Item 1"/>
<TextBlock Text="Item 2"/>
<TextBlock Text="Item 3"/>
</ItemsControl>
Here's another option that doesn't require making a custom control and is more typesafe than doing the ItemsControl thing (if type safety is something you want..perhaps not):
...Use an attached property!
Create an attached property of the appropriate type. We happened to need a text control so I did a string TextContent attached property. Then create a TemplateBinding to it from the template, and when instantiating in Xaml set it there as well. Works nicely.