How to give Style to WPF Toolkit Chart - wpf

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;
}
}

Related

Horizontal accordion control?

I want a control whose behavior is as follows:
Act like a Grid
Each child control is embedded in a horizontal Expander (whose header is binded to the control's Tag property)
Each of these Expander has its own ColumnDefinition
Only one of these expanders can be expanded at a time
Non-expanded Expanders' ColumnDefinition have a width set to Auto
The expanded Expander's one is * (Star)
It has to use these exact controls (Grid/Expander), and not some custom ones, so my application's style can automatically apply to them.
I can't seem to find something already made, no built-in solution seems to exist (if only there was a "filling" StackPanel...) and the only solution I can come up with is to make my own Grid implementation, which seems... daunting.
Is there a solution to find or implement such a control?
Here's what I have for now. It doesn't handle the "single-expanded" nor the filling. I don't really know if StackPanel or Expander is to blame for this.
<ItemsControl>
<ItemsControl.Resources>
<DataTemplate x:Key="verticalHeader">
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type Expander}}, Path=Header}" />
</DataTemplate>
<Style TargetType="{x:Type Expander}"
BasedOn="{StaticResource {x:Type Expander}}">
<Setter Property="HeaderTemplate"
Value="{StaticResource verticalHeader}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="ExpandDirection"
Value="Right" />
</Style>
</ItemsControl.Resources>
<ItemsControl.Template>
<ControlTemplate>
<!-- Damn you, StackPanel! -->
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ControlTemplate>
</ItemsControl.Template>
<Expander Header="Exp1">
<TextBlock Text="111111111" Background="Red"/>
</Expander>
<Expander Header="Exp2">
<TextBlock Text="222222222" Background="Blue"/>
</Expander>
<Expander Header="Exp3">
<TextBlock Text="333333333" Background="Green"/>
</Expander>
</ItemsControl>
My first thought is to perform this kind of action with a Behavior. This is some functionality that you can add to existing XAML controls that give you some additional customization.
I've only looked at it for something that's not using an ItemsSource as I used a Grid with Columns etc. But in just a plain grid, you can add a behavior that listens for it's childrens Expanded and Collapsed events like this:
public class ExpanderBehavior : Behavior<Grid>
{
private List<Expander> childExpanders = new List<Expander>();
protected override void OnAttached()
{
//since we are accessing it's children, we have to wait until initialise is complete for it's children to be added
AssociatedObject.Initialized += (gridOvject, e) =>
{
foreach (Expander expander in AssociatedObject.Children)
{
//store this so we can quickly contract other expanders (though we could just access Children again)
childExpanders.Add(expander);
//track expanded events
expander.Expanded += (expanderObject, e2) =>
{
//contract all other expanders
foreach (Expander otherExpander in childExpanders)
{
if (expander != otherExpander && otherExpander.IsExpanded)
{
otherExpander.IsExpanded = false;
}
}
//set width to star for the correct column
int index = Grid.GetColum(expanderObject as Expander);
AssociatedObject.ColumnDefinitions[index].Width = new GridLength(1, GridUnitType.Star);
};
//track Collapsed events
expander.Collapsed += (o2, e2) =>
{
//reset all to auto
foreach (ColumnDefinition colDef in AssociatedObject.ColumnDefinitions)
{
colDef.Width = GridLength.Auto;
}
};
}
};
}
}
Use it like this, note you have to add System.Windows.Interactivity as a reference to your project:
<Window ...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="...">
<Window.Resources>
<DataTemplate x:Key="verticalHeader">
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type Expander}}, Path=Header}" />
</DataTemplate>
<Style TargetType="{x:Type Expander}"
BasedOn="{StaticResource {x:Type Expander}}">
<Setter Property="HeaderTemplate"
Value="{StaticResource verticalHeader}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="ExpandDirection"
Value="Right" />
</Style>
<local:ExpanderBehavior x:Key="ExpanderBehavor"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<i:Interaction.Behaviors>
<local:ExpanderBehavior/>
</i:Interaction.Behaviors>
<Expander Header="Exp1">
<TextBlock Text="111111111" Background="Red"/>
</Expander>
<Expander Header="Exp2" Grid.Column="1">
<TextBlock Text="222222222" Background="Blue"/>
</Expander>
<Expander Header="Exp3" Grid.Column="2">
<TextBlock Text="333333333" Background="Green"/>
</Expander>
</Grid>
</Window>
The final result:
Edit: Working with ItemsControl - add it to the grid that hosts the items, and add a little to manage the column mapping
public class ItemsSourceExpanderBehavior : Behavior<Grid>
{
private List<Expander> childExpanders = new List<Expander>();
protected override void OnAttached()
{
AssociatedObject.Initialized += (gridOvject, e) =>
{
//since we are accessing it's children, we have to wait until initialise is complete for it's children to be added
for (int i = 0; i < AssociatedObject.Children.Count; i++)
{
Expander expander = AssociatedObject.Children[i] as Expander;
//sort out the grid columns
AssociatedObject.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
Grid.SetColumn(expander, i);
childExpanders.Add(expander);
//track expanded events
expander.Expanded += (expanderObject, e2) =>
{
foreach (Expander otherExpander in childExpanders)
{
if (expander != otherExpander && otherExpander.IsExpanded)
{
otherExpander.IsExpanded = false;
}
}
//set width to auto
int index = AssociatedObject.Children.IndexOf(expanderObject as Expander);
AssociatedObject.ColumnDefinitions[index].Width = new GridLength(1, GridUnitType.Star);
};
//track Collapsed events
expander.Collapsed += (o2, e2) =>
{
foreach (ColumnDefinition colDef in AssociatedObject.ColumnDefinitions)
{
colDef.Width = GridLength.Auto;
}
};
}
};
}
}
Used:
<ItemsControl>
<ItemsControl.Template>
<ControlTemplate>
<Grid IsItemsHost="True">
<i:Interaction.Behaviors>
<local:ItemsSourceExpanderBehavior/>
</i:Interaction.Behaviors>
</Grid>
</ControlTemplate>
</ItemsControl.Template>
<Expander Header="Exp1">
<TextBlock Text="111111111" Background="Red"/>
</Expander>
<Expander Header="Exp2">
<TextBlock Text="222222222" Background="Blue"/>
</Expander>
<Expander Header="Exp3">
<TextBlock Text="333333333" Background="Green"/>
</Expander>
</ItemsControl>
Note, that you'll have to add some logic to manage new/removed children if you have any changes to your ItemsSource!

Style embedded ControlTemplate using Style

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

DataGrid custom CanUserAddRows implementation

I want to implement a custom add row button to a DataGrid (it's a long story). I added the button in template, and define an attached property, and I can get the button's click. But I can not add a new row in a generic way -not for a specified type-. I know that I can do similar thing in ViewModel, but I'm looking to do this in templates and attached properties. Here is my try; Any idea to complete this?
XAML:
<Style x:Key="{x:Type DataGrid}" TargetType="{x:Type DataGrid}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGrid}">
<Border x:Name="border">
<ScrollViewer x:Name="DG_ScrollViewer" Focusable="false">
<ScrollViewer.Template>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button Focusable="false"
Command="{x:Static DataGrid.SelectAllCommand}" />
<DataGridColumnHeadersPresenter
x:Name="PART_ColumnHeadersPresenter"
Grid.Column="1" />
<Grid Grid.ColumnSpan="2" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="22" />
</Grid.RowDefinitions>
<ScrollContentPresenter
x:Name="PART_ScrollContentPresenter" />
<!-- THIS IS MY CUSTOM BUTTON TO ADD NEW ROW -->
<Button x:Name="PART_AddRowButton"
Content="Add"/>
</Grid>
<ScrollBar x:Name="PART_VerticalScrollBar"/>
<Grid Grid.Column="1" Grid.Row="2">
<ScrollBar x:Name="PART_HorizontalScrollBar"/>
</Grid>
</Grid>
</ControlTemplate>
</ScrollViewer.Template>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
C#:
public class DataGridHelper {
public static readonly DependencyProperty CanUserAddRowsProperty
= DependencyProperty.RegisterAttached(
"CanUserAddRows", typeof(bool), typeof(DataGridHelper),
new FrameworkPropertyMetadata(default(bool), CanUserAddRowsChanged));
[AttachedPropertyBrowsableForType(typeof(DataGrid))]
public static bool GetCanUserAddRows(DependencyObject obj) {
return (bool)obj.GetValue(CanUserAddRowsProperty);
}
[AttachedPropertyBrowsableForType(typeof(DataGrid))]
public static void SetCanUserAddRows(DependencyObject obj, bool value) {
obj.SetValue(CanUserAddRowsProperty, value);
}
private static void CanUserAddRowsChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e) {
var dataGrid = d as DataGrid;
if (dataGrid == null)
return;
bool oldValue = (bool)e.OldValue,
newValue = (bool)e.NewValue;
if (newValue == oldValue)
return;
if (newValue) {
dataGrid.Loaded += CanUserAddRowsDataGridLoaded;
} else {
dataGrid.Loaded -= CanUserAddRowsDataGridLoaded;
}
}
private static void CanUserAddRowsDataGridLoaded(object sender, RoutedEventArgs e) {
var dataGrid = sender as DataGrid;
if (dataGrid == null)
return;
if (dataGrid.Style == null)
return;
var rootTemplate = dataGrid.Template;
if (rootTemplate == null)
return;
var scroll = rootTemplate.FindName("DG_ScrollViewer", dataGrid) as ScrollViewer;
if (scroll == null)
return;
var scrollTemplate = scroll.Template;
if (scrollTemplate == null)
return;
var button = scrollTemplate.FindName("PART_AddRowButton", scroll) as ButtonBase;
if (button == null)
return;
if (GetCanUserAddRows(dataGrid)) {
button.Click += AddRowClicked;
} else {
button.Click -= AddRowClicked;
}
}
private static void AddRowClicked(object sender, RoutedEventArgs e) {
var button = ((ButtonBase)sender);
var parent = VisualTreeHelper.GetParent(button);
while (!(parent is DataGrid))
parent = VisualTreeHelper.GetParent(parent);
var source = ((DataGrid)parent).Items.Add(...) // now what???
}
}
Well, as you can see, I got access to DataGrid after button got clicked; But what is next? How can I force the DataGrid to show NewItemPlaceHolder?
Typically in WPF, we bind collections (preferably collections that support change notification like ObservableCollection) of data objects to UI controls. Rather than adding new items to the UI controls, we add the items to the collections in the code behind/view model. As long as the collection support change notification, the UI controls will be automatically updated.
So to add a new row to your DataGrid, you need to add a new item to your collection:
dataCollection.Add(new DataType());
You should be able to access your data bound collection in your AttachedProperty using:
var dataCollection = (DataCollectionType)dataGrid.ItemsSource;
I believe that you can also use:
dataGrid.Items.Add(new DataType());
although this method is not recommended.

WPF Expand RadPanelBarItem only in available space

I have a RadPanelBar with each RadPanelItem having a list of entities(Different list in each Item). Each item in the List is shown as a GroupBox. With a large number of items the RadPanelBar has to be scrolled in order for the other RadPanelBarItems to be visible. I want it such that the scrollbar appears within each RadPanelBarItem so that all the RadPanelBarItems will be visible on the screen at the same time and if the contents of an item are too long, the user has to scroll only within each RadPanelBarItem.
I'm using the ItemsSource property of each RadPanelBarItem and setting its ItemTemplate to display the GroupBoxes.
Is there a good way to do this, so that everything(Height and such) is kept dynamic?
Thanks!
There seems to be no easy way to do this. I got the following response from Telerik when I asked a similar question:
If I got your case correctly you have several options:
1) Set the size for PanelBarItem. This way you will limit how big they could be. If
you match items summed size to the size of the PanelBar you should
eliminate the clippings.
2) Customize the PanelBar and PanelBarItem control templates in order
to support automatic proportional sizing. In this case you should
remove the ScrollViewer from PanelBar control template and add a
ScrollViewer in the top level PanelBarItem control template (around
the ItemsPresenter). Also you should change RadPanelBar ItemsPanel to
an appropriate panel. Probably. it is going to be a custom panel in
order to measure the items with equal sizes vertically.
I have made a try to do a custom Panel and modifying the control template. I have got it working but it's quite a lot of code, but here goes:
DistributedHeightPanel.cs
This is the custom Panel which do the layout and distributes the available height.
/// <summary>
/// Panel that distributes the available height amongst it's children (like a vertical StackPanel but the children are not allowed to be placed "outside" the parent's visual area).
/// </summary>
public class DistributedHeightPanel : Panel
{
/// <summary>
/// If set to a positive number, no child will get less height than specified.
/// </summary>
public double ItemsMinHeight
{
get { return (double)GetValue(ItemsMinHeightProperty); }
set { SetValue(ItemsMinHeightProperty, value); }
}
public static readonly DependencyProperty ItemsMinHeightProperty =
DependencyProperty.Register("ItemsMinHeight", typeof(double), typeof(DistributedHeightPanel), new UIPropertyMetadata(0.0));
public DistributedHeightPanel()
: base()
{
}
protected override Size MeasureOverride(Size availableSize)
{
List<double> heights = new List<double>();
//Find out how much height each child desire if it was the only child
foreach (UIElement child in InternalChildren)
{
child.Measure(availableSize);
heights.Add(child.DesiredSize.Height);
}
//Calculate ratio
double ratio = GetRatio(availableSize.Height, heights);
//Do the "real" Measure
foreach (UIElement child in InternalChildren)
{
double actualHeight = child.DesiredSize.Height;
if (ratio < 1)
{
//If ratio < 1 then the child can't have all the space it wants, calculate the new height
actualHeight = child.DesiredSize.Height * ratio;
}
if (ItemsMinHeight > 0 && actualHeight < ItemsMinHeight)
{
//If ItemsMinHeight is set and the child is to small, then set the childs height to ItemsMinHeight
actualHeight = ItemsMinHeight;
}
child.Measure(new Size(availableSize.Width, actualHeight));
}
return availableSize;
}
/// <summary>
/// Calculates the ratio for fitting all heights in <paramref name="heightsToDistribute"/> in the total available height (as supplied in <paramref name="availableHeight"/>)
/// </summary>
private double GetRatio(double availableHeight, List<double> heightsToDistribute)
{
//Copy the heights list
List<double> heights = new List<double>(heightsToDistribute);
double desiredTotalHeight = heights.Sum();
//If no height is desired then return 1
if (desiredTotalHeight <= 0)
return 1;
//Calculate ratio
double ratio = availableHeight / desiredTotalHeight;
//We only want to compress so if ratio is higher than 1 return 1
if (ratio > 1)
{
return 1;
}
//Check if heights become too small when the ratio is used
int tooSmallCount = heights.Count(d => d * ratio < ItemsMinHeight);
//If no or all all heights are too small: return the calculated ratio
if (tooSmallCount == 0 || tooSmallCount == heights.Count)
{
return ratio;
}
else
{
//Remove the items which becomes too small and get a ratio without them (they will get ItemsMinHeight)
heights.RemoveAll(d => d * ratio < ItemsMinHeight);
return GetRatio(availableHeight - ItemsMinHeight * tooSmallCount, heights);
}
}
protected override Size ArrangeOverride(Size finalSize)
{
//Arrange all children like a vertical StackPanel
double y = 0;
foreach (UIElement child in InternalChildren)
{
//child.DesiredSize.Height contains the correct value since it was calculated in MeasureOverride
child.Arrange(new Rect(0, y, finalSize.Width, child.DesiredSize.Height));
y += child.DesiredSize.Height;
}
return finalSize;
}
}
MainWindow.xaml
Contains the control template as a style named DistributedHeightRadPanelBarStyle and a RadPanelBar for testing.
<Window x:Class="WpfApplication9.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication9"
Title="MainWindow" Height="350" Width="525" xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation">
<Window.Resources>
<Style x:Key="DistributedHeightRadPanelBarStyle" TargetType="{x:Type telerik:RadPanelBar}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<local:DistributedHeightPanel ItemsMinHeight="22" /> <!-- 22 is fine for collapsed headers -->
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type telerik:RadPanelBar}">
<Grid>
<telerik:LayoutTransformControl x:Name="transformationRoot" IsTabStop="False">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<!-- <ScrollViewer x:Name="ScrollViewer" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalScrollBarVisibility="Auto" IsTabStop="False" Padding="{TemplateBinding Padding}" VerticalScrollBarVisibility="Auto" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">-->
<telerik:StyleManager.Theme>
<telerik:Office_BlackTheme/>
</telerik:StyleManager.Theme>
<ItemsPresenter/>
<!--</ScrollViewer>-->
</Border>
</telerik:LayoutTransformControl>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Orientation" Value="Horizontal">
<Setter Property="LayoutTransform" TargetName="transformationRoot">
<Setter.Value>
<RotateTransform Angle="-90"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Orientation" Value="Vertical"/>
</Style>
</Window.Resources>
<Grid>
<telerik:RadPanelBar Style="{StaticResource ResourceKey=DistributedHeightRadPanelBarStyle}" VerticalAlignment="Top" ExpandMode="Multiple" HorizontalAlignment="Stretch">
<telerik:RadPanelBarItem DropPosition="Inside" Header="A - Colors" IsExpanded="True">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel>
<TextBlock Height="100" Background="AliceBlue" Text="I'm AliceBlue" />
<TextBlock Height="100" Background="AntiqueWhite" Text="I'm AntiqueWhite" />
</StackPanel>
</ScrollViewer>
</telerik:RadPanelBarItem>
<telerik:RadPanelBarItem DropPosition="Inside" Header="B - Colors" IsExpanded="True">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel>
<TextBlock Height="100" Background="Beige" Text="I'm Beige" />
<TextBlock Height="100" Background="Bisque" Text="I'm Bisque" />
</StackPanel>
</ScrollViewer>
</telerik:RadPanelBarItem>
<telerik:RadPanelBarItem DropPosition="Inside" Header="C - Colors">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel>
<TextBlock Height="100" Background="CadetBlue" Text="I'm CadetBlue" />
<TextBlock Height="100" Background="Chartreuse" Text="I'm Chartreuse" />
</StackPanel>
</ScrollViewer>
</telerik:RadPanelBarItem>
</telerik:RadPanelBar>
</Grid>
Maybe this solution is too late for you to use but hopefully someone will find it useful.

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}}" />

Resources