Style embedded ControlTemplate using Style - wpf

Im using Infragistics controls with Theming. The Template property is set on a Trigger.
That Template is configured further up the hierarchy so I cannot edit directly but I want to change one of the properties set.
e.g.
Template set on a trigger (truncated)
<Style x:Key="FxtPaneTabItemStyle" TargetType="{x:Type igDock:PaneTabItem}">
<Setter Property="TextBlock.TextTrimming" Value="CharacterEllipsis" />
<Style.Triggers>
<Trigger Property="igDock:XamDockManager.PaneLocation" Value="Unpinned">
<Setter Property="Template" Value="{DynamicResource {x:Static igDock:PaneTabItem.DockableTabItemTemplateKey}}" />
</Trigger>
</Style.Triggers>
</Style>
The template configured in unreachable code (truncated)
<ControlTemplate x:Key="{x:Static igDock:PaneTabItem.DockableTabItemTemplateKey}" TargetType="{x:Type igDock:PaneTabItem}">
<Border x:Name="ctrlBorder" SnapsToDevicePixels="true" MinHeight="25">
<controls:CardPanel>
<controls:CardPanel x:Name="Background">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Height="25">
<Border x:Name="Border" Margin="0,0,0,0" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" SnapsToDevicePixels="True"/>
<Border x:Name="HighlightBorder" Margin="0" BorderBrush="{DynamicResource {x:Static igDock:DockManagerBrushKeys.TabbedListNotActiveInnerBorderFillKey}}" BorderThickness="0" SnapsToDevicePixels="True"/>
</Grid>
</controls:CardPanel>
</Border>
<ControlTemplate.Triggers>
</ControlTemplate.Triggers>
I only want to override the Border (x:Name="ctrlBorder") MinHeight property. Is this possible without replicating the entire ControlTemplate in my code base. and changing this single property?

as far as I know, you can't change the template, but you can create a custom behavior (or add code as on the code behind) on the code that you used that control.
On that code, go over the control visual hierarchy and find the border by name. than you can change its properties.
It's important that you will try to find the elements (border on your case) on the visual tree after the Loaded event has accor on that object, because you need that the visual will be created already
Finding visual elements on the visual hierarchy:
public static List<T> FindVisualChildren<T>(DependencyObject depObj, bool searchWithinAFoundT = true) where T : DependencyObject
{
List<T> list = new List<T>();
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
list.Add((T)child);
// this means that an element is not expected to contain elements of his type
if (!searchWithinAFoundT) { continue; }
}
List<T> childItems = FindVisualChildren<T>(child, searchWithinAFoundT);
if (childItems != null && childItems.Count > 0)
{
foreach (var item in childItems)
{
list.Add(item);
}
}
}
}
return list;
}
It's a little dirty but it can help on a specific cases

Related

WPF DocumentViewer loses custom style after internal link use

Hi want to build a small application, that allows to navigate through filesystem and displays several documents. One type of document i want to show, is xps. DocumentViewer is doing well. In combination with a Frame the viewer can handle internal links (included in the xps documents.). For my Application i build a custom toolbar (zoom, page, fitsize ...), to have a one toolbar for every kind of document. So i needed to remove the toolbar of the documentViewer. Below is the code.
<Style x:Key="{x:Type DocumentViewer}"
TargetType="{x:Type DocumentViewer}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DocumentViewer}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Focusable="False">
<ScrollViewer
CanContentScroll="true"
HorizontalScrollBarVisibility="Auto"
x:Name="PART_ContentHost"
IsTabStop="true">
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
That works fine, but after activating a link in the xps, the DocumentViewer Toolbar appears again. How to avoid that?
The problem is that the navigation service creates a new standard DocumentViewer after clicking on a link for the first time. This happens even when you use a component derived from DocumentViewer in your XAML.
You can work around this issue by manually resetting the style in your navigation container's LayoutUpdated event
XAML
<Frame LayoutUpdated="OnFrameLayoutUpdated">
<Frame.Content>
<DocumentViewer ... />
</Frame.Content>
</Frame>
Code behind
private void OnFrameLayoutUpdated(object sender, EventArgs e)
{
var viewer = GetFirstChildByType<DocumentViewer>(this);
if (viewer == null) return;
viewer.Style = (Style) FindResource("DocumentViewerStyle");
}
private T GetFirstChildByType<T>(DependencyObject prop) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(prop); i++)
{
DependencyObject child = VisualTreeHelper.GetChild((prop), i) as DependencyObject;
if (child == null)
continue;
T castedProp = child as T;
if (castedProp != null)
return castedProp;
castedProp = GetFirstChildByType<T>(child);
if (castedProp != null)
return castedProp;
}
return null;
}

How to give Style to WPF Toolkit Chart

I am using WPF Toolkit Chart with PieChart in my WPF Application.
I want to change by default white background to Transparent in PieChart Picture..
How to give Style to Achieve that
WPF was designed to allow you to style controls through XAML; not code. Making the plot area and legend transparent in a pie chart is also possible through styling. Unfortunately, the border around the plot area cannot be controlled using a property and instead you have to modify the entire control template. In the end using styling is probably just as tedious as writing code behind that modifies the visual tree, but to me at least, it still feels like a cleaner approach.
<chartingToolkit:Chart>
<chartingToolkit:Chart.PlotAreaStyle>
<Style TargetType="Grid">
<Setter Property="Background" Value="Transparent"/>
</Style>
</chartingToolkit:Chart.PlotAreaStyle>
<chartingToolkit:Chart.LegendStyle>
<Style TargetType="visualizationToolkit:Legend">
<Setter Property="Margin" Value="15,0"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Background" Value="Transparent"/>
</Style>
</chartingToolkit:Chart.LegendStyle>
<chartingToolkit:Chart.Style>
<Style TargetType="chartingToolkit:Chart">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="chartingToolkit:Chart">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<visualizationToolkit:Title Content="{TemplateBinding Title}" Style="{TemplateBinding TitleStyle}" />
<!-- Use a nested Grid to avoid possible clipping behavior resulting from ColumnSpan+Width=Auto -->
<Grid Grid.Row="1" Margin="0,15,0,15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<visualizationToolkit:Legend x:Name="Legend" Title="{TemplateBinding LegendTitle}" Style="{TemplateBinding LegendStyle}" Grid.Column="1" />
<chartingprimitives:EdgePanel x:Name="ChartArea" Style="{TemplateBinding ChartAreaStyle}">
<Grid Canvas.ZIndex="-1" Style="{TemplateBinding PlotAreaStyle}" />
<!--<Border Canvas.ZIndex="10" BorderBrush="#FF919191" BorderThickness="1" />-->
</chartingprimitives:EdgePanel>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</chartingToolkit:Chart.Style>
<chartingToolkit:PieSeries ... />
</chartingToolkit:Chart>
The PlotAreaStyle and LegendStyle are modified to make them transparent. The border around the plot area is removed by modifying the ControlTemplate of the chart and simply commenting out the offending Border element.
If you looked at visual tree you find out that you must change Background property of grid and border to change background to transparent (elements highlighted in yellow in the below picture).
To do that you can change color in Loaded event. First you must find EdgePanel with name ChartArea and after that you must change color of grid and border. If you want to set also background of Legend to transparent you must find Legend element and set appropriate properties.
<DVC:Chart Canvas.Top="80" Canvas.Left="10" Name="mcChart"
Width="400" Height="250"
Background="Orange"
Loaded="mcChart_Loaded">
<DVC:Chart.Series>
<DVC:PieSeries Title="Experience"
ItemsSource="{StaticResource FruitCollection}"
IndependentValueBinding="{Binding Path=Name}"
DependentValueBinding="{Binding Path=Share}">
</DVC:PieSeries>
</DVC:Chart.Series>
</DVC:Chart>
Code-behind:
private void mcChart_Loaded(object sender, RoutedEventArgs e)
{
EdgePanel ep = VisualHelper.FindChild<EdgePanel>(sender as Chart, "ChartArea");
if (ep != null)
{
var grid = ep.Children.OfType<Grid>().FirstOrDefault();
if (grid != null)
{
grid.Background = new SolidColorBrush(Colors.Transparent);
}
var border = ep.Children.OfType<Border>().FirstOrDefault();
if (border != null)
{
border.BorderBrush = new SolidColorBrush(Colors.Transparent);
}
}
Legend legend = VisualHelper.FindChild<Legend>(sender as Chart, "Legend");
if (legend != null)
{
legend.Background = new SolidColorBrush(Colors.Transparent);
legend.BorderBrush = new SolidColorBrush(Colors.Transparent);
}
}
Helper class to find child element in this case EdgePanel:
class VisualHelper
{
public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject
{
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
T childType = child as T;
if (childType == null)
{
foundChild = FindChild<T>(child, childName);
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
if (frameworkElement != null && frameworkElement.Name == childName)
{
foundChild = (T)child;
break;
}
}
else
{
foundChild = (T)child;
break;
}
}
return foundChild;
}
}

Attached Property Values are not passed to the View - style for first/last item of list

My intention is to define a dependency property Position for list-elements to style them different if they are on the edge.
I have a dependency property (along with its default get and set methods that are not shown):
public static readonly DependencyProperty PositionProperty =
DependencyProperty.RegisterAttached(
"Position",
typeof(Position),
typeof(ClientView),
new FrameworkPropertyMetadata(
Position.Normal));
a TabControl:
<TabControl x:Name="Items" ItemContainerStyle="{DynamicResource TabItem}"/>
and a template for those TabItems:
<Style x:Key="TabItem" TargetType="{x:Type TabItem}">
... <Setter Property="Template"> <Setter.Value>
<Grid SnapsToDevicePixels="true">
<ControlTemplate TargetType="{x:Type TabItem}">
<Border x:Name="Bd">
<StackPanel Orientation="Horizontal">
<TextBlock
Text="{Binding Position, RelativeSource={RelativeSource
AncestorType={x:Type client:ClientView}}}"/>
<ContentPresenter x:Name="Content" ContentSource="Header"/>
</StackPanel>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="client:ClientView.Position" Value="Last">
<Setter Property="CornerRadius" TargetName="Bd" Value="0,0,0,4"/>
</Trigger>
...
On the codebehind of my ClientView class, i fill those properties when the items generator tells me that he created those tabitems:
var gen = Items.ItemContainerGenerator;
gen.StatusChanged += (sender, args) =>
{
if (gen.Status == GeneratorStatus.ContainersGenerated)
{
var cnt = Items.Items.Count;
if (cnt > 0)
{
if (cnt == 1)
{
gen.ContainerFromItem(Items.Items[0])
.SetValue(PositionProperty, Position.Normal);
}
else
{
gen.ContainerFromItem(Items.Items[0])
.SetValue(PositionProperty, Position.First);
if (cnt > 2)
{
for (int i = 1; i < cnt - 2; i++)
{
gen.ContainerFromItem(Items.Items[i])
.SetValue(PositionProperty, Position.Normal);
}
}
gen.ContainerFromItem(Items.Items[cnt - 1])
.SetValue(PositionProperty, Position.Last);
}
}
}
};
When i run this code, i can debug those event and see the correct values are set, but they never appear in the view. The text of the textbox always shows "Normal" which is the default value.
What am i doing wrong here?
What if you bind bind the TextBlock.Textproperty like this
<TextBlock Text="{Binding
Path=(ClientView.Position),
RelativeSource={RelativeSource Mode=TemplatedParent}}" />
since the attached property is set on the styled TabItem, i.e. the TemplatedParent.
EDIT: What if you also set the Border.CornerRadius property by a binding with a binding converter that converts from Position to an appropriate CorderRadius value:
<Border CornerRadius="{Binding
Path=(ClientView.Position),
RelativeSource={RelativeSource Mode=TemplatedParent},
Converter={StaticResource PositionToCornerRadiusConverter}}" />

QStackedLayout equivalent in WPF

I'm a quite experienced Qt programmer and I used QStackedLayout a lot to show different widgets in the main window. Can someone please point me to an equivalent construct in WPF: Is there such a thing like QStackedLayout? If not, how is this pattern used in WPF?
Basically I have a WPF Ribbon Application and if the Ribbon Group is switched the corresponding "widget" / XAML should be displayed in the remaining area ("content").
Thanks, dude.
There isn't a native panel or control that would do that, but you could leverage the TabControl to accomplish it. You'd need to use a custom Style, though like so:
<Style x:Key="NoTabsTabControlStyle" TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Border Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
KeyboardNavigation.TabNavigation="Local"
KeyboardNavigation.DirectionalNavigation="Contained">
<ContentPresenter x:Name="PART_SelectedContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled"
Value="false">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then use it like:
<TabControl Style="{StaticResource NoTabsTabControlStyle}">
<TabItem Content="One" />
<TabItem Content="Two" />
</TabControl>
Then to display one set of content, you'd set SelectedIndex on the TabControl.
A bit late for topic starter but may be of some help to people who comes here searching for WPF version of QStackedLayout, like me.
I used the very simplified implementation of WPF layout example, throwing out virtually all things layout.
The component is based on StackLayout to allow for simple visual design, in design time it just behaves like normal stack panel.
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
namespace org.tequilacat.stacklayout {
/// <summary>
/// QStackedLayout implementation for WPF
/// only one child is displayed extended to the panel size.
/// In design time it behaves like stack panel
/// </summary>
public class StackLayoutPanel : StackPanel {
private bool isDesignTime() {
return System.ComponentModel.DesignerProperties.GetIsInDesignMode(this);
}
private bool useBaseBehaviour() {
return isDesignTime();
}
// in runtime just return the given arg
protected override Size MeasureOverride(Size availableSize) {
if (useBaseBehaviour()) {
return base.MeasureOverride(availableSize);
}
return availableSize;
}
// in runtime arrange all children to the given arg
protected override Size ArrangeOverride(Size finalSize) {
if (useBaseBehaviour()) {
return base.ArrangeOverride(finalSize);
}
foreach (UIElement child in InternalChildren) {
child.Arrange(new Rect(finalSize));
}
return finalSize;
}
}
}
The XAML is
<Window ... xmlns:uilib="clr-namespace:org.tequilacat.stacklayout">
<uilib:StackLayoutPanel >
<StackPanel Name="projectPropertyPanel"> ... </StackPanel>
<StackPanel Name="configurationPanel"> ... </StackPanel>
<StackPanel Name="casePanel"> ... </StackPanel>
</uilib:StackLayoutPanel>
In Run time the visible component is chosen via Visibility property (here depends on my business logic, uiState can take 3 values activating one of panels). It's very basic, one can implement own CurrentPage property or so, I just kept it simple:
projectPropertyPanel.Visibility = (uiState == UiState.ProjectProperties) ?
Visibility.Visible : Visibility.Collapsed;
configurationPanel.Visibility = (uiState == UiState.ConfigurationSelected) ?
Visibility.Visible : Visibility.Collapsed;
casePanel.Visibility = (uiState == UiState.CaseSelected) ?
Visibility.Visible : Visibility.Collapsed;

TemplateBinding doesn't bind to effect's property?

Imagine a control named Testko like this:
public class Testko: Control
{
public static readonly DependencyProperty TestValueProperty;
static Testko()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Testko), new FrameworkPropertyMetadata(typeof(Testko)));
TestValueProperty = DependencyProperty.Register("TestValue", typeof(double), typeof(Testko), new UIPropertyMetadata((double)1));
}
public double TestValue
{
get { return (double)GetValue(TestValueProperty); }
set { SetValue(TestValueProperty, value); }
}
}
Nothing fancy, just an empty control with a single double property with a default value set to (double)1.
Now, image a generic style like this:
<Style TargetType="{x:Type local:Testko}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Testko}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel Orientation="Vertical">
<StackPanel.Effect>
<BlurEffect Radius="{TemplateBinding TestValue}" />
</StackPanel.Effect>
<Button Content="{TemplateBinding TestValue}" Margin="4" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, the problem is that Radius property is never bound for some reason. Wheras Button's content is properly bound to TestValue property.
I am sure I am missing something obvious. Or not?
If it is obvious, it is not to me :-)
My favorite book (WPF Unleashed) mentions that sometimes TemplatedBinding doesn't work (but the enumerated reasons don't match your circumstances).
But TemplatedBinding is a shortcut for:
{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TestValue}
I reproduced your case, i.e. changing TestValue only has effect on the button.
After replacing the TemplatedBinding by this, I get the desired effect.

Resources