Continue WPF gridview alteration - wpf

Is it possible to continue the alteration styles in a gridview even when there are no items?
As you can see, after the last item, the pattern stops.

Yes, WPF provides a rather elegant way to implement this because its templating mechanism allows you to fill the unused area in a GridView with whatever you like.
All you need to do is modify the ListView template to paint the unused section of the with a VisualBrush that typically consists of two GridViewItems stacked vertically (in the general case it will be AlternationCount GridViewItems).
The only complexity is choosing which color to start with when painting the unused section of the ScrollViewer. This is calculated as Items.Count modulo AlternationCount. The solution is to create a simple Control that does this calculation and use it in our ListView template. For the sake of my explanation I will call the control "ContinueAlternation".
The ListView template which would be mostly the default template with a local:ContinueAlternation control added below the ScrollViewer using a DockPanel, like this:
<ControlTemplate TargetType="{x:Type ListView}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="True">
<DockPanel>
<ScrollViewer DockPanel.Dock="Top"
Style="{DynamicResource {x:Static GridView.GridViewScrollViewerStyleKey}}"
Padding="{TemplateBinding Padding}">
<ItemsPresenter SnapsToDevicePixels="True" />
</ScrollViewer>
<local:ContinueAlternation
ItemContainerStyle="{TemplateBinding ItemContainerStyle}"
AlternationCount="{TemplateBinding AlternationCount}"
ItemsCount="{Binding Items.Count,
RelativeSource={RelativeSource TemplatedParent}}" />
</DockPanel>
</Border>
</ControlTemplate>
The ContinueAlternation control will be displayed as a Rectangle painted with a tiled VisualBrush containing an ItemsControl that shows dummy rows, as follows:
<ControlTemplate TargetType="{x:Type local:ContinueAlternation}">
<Rectangle>
<Rectangle.Fill>
<VisualBrush TileMode="Tile" Stretch="None"
ViewPortUnits="Absolute"
ViewPort="{TemplateBinding ViewportSize}">
<ItemsControl x:Name="PART_ItemsControl"
ItemsSource="{Binding}" />
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
</ControlTemplate>
The DataContext here will be an array of dummy ListViewItem generated in code-behind from the given AlternationCount and ItemsCount:
public class ContinueAlternation
{
public Style ItemsContainerStyle ... // Declare as DependencyProperty using propdp snippet
public int AlternationCount ... // Declare as DependencyProperty using propdp snippet
public int ItemsCount ... // Declare as DependencyProperty using propdp snippet
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if(e.Property==ItemsContainerStyleProperty ||
e.Property==AlternationCountProperty ||
e.Property==ItemsCountProperty)
{
// Here is where we build the items for display
DataContext =
from index in Enumerable.Range(ItemsCount,
ItemsCount + AlternationCount)
select BuildItem( index % AlternationCount);
}
}
ListViewItem BuildItem(int alternationIndex)
{
var item = new ListViewItem { Style = ItemsContainerStyle };
ItemsControl.SetAlternationIndex(item, alternationIndex);
return item;
}
protected override Size MeasureOverride(Size desiredSize)
{
var ic = (ItemsControl)GetTemplateChild("PART_ItemsControl");
ic.Width = desiredSize.Width;
Size result = base.MeasureOverride(desiredSize);
ViewportSize = new Size(ic.DesiredSize);
return result;
}
public Size ViewportSize ... // Declare as DependencyProperty using propdp snippet
}
Note that this same code could be written with PropertyChangedCallback instead of OnPropertyChanged.
You also need to do something to make sure the blank rows are the desired height. The easiest way to do this is to set either MinHeight or Content in your ItemsContainerStyle. Alternatively ContinueAlternation could set the height when it constructs each ListViewItem.
I typed all this code off the top of my head, but it is similar to code I've written and used before so it ought to work basically as-is.

Related

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.

capture events from lookless control button

I have create a lookless control to be used in a Silverlight 4 project. This control contains a button and I would like to capture the click event. The Generic.xaml contains
<Style TargetType="TU:MyControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TU:MyControl" >
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" d:DesignWidth="550" d:DesignHeight="228">
<Grid Background="Silver">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150*"/>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="150*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Margin="2" BorderBrush="DarkGray" BorderThickness="3"></Border>
<Border Grid.Column="2" Margin="2" BorderBrush="DarkGray" BorderThickness="3"></Border>
<StackPanel Grid.Column="1">
<Button Name="PART_MyClick" Height="32" Width="32" Margin="0,8,0,0"></Button>
</StackPanel>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
After researching the problem I beleive that I have to add the following attribute to my control class
[TemplatePart(Name = "PART_MyClick", Type = typeof(Button))]
Then in my controls constructor I have added the following code
var myClick = GetTemplateChild("PART_MyClick") as Button;
if(myClick != null)
{
myClick.Click += (o, e) => DoThings();
}
when run though the myClick variable is always null so the event handler never gets attached. Could you please tell me where I am going wrong? Im a newbie so if this is the wrong approach completly then any advise on the correct approach would also be greatfully received
Override the OnApplyTemplate method and put your code there instead of the control's constructor:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var myClick = GetTemplateChild("PART_MyClick") as Button;
if(myClick != null)
{
myClick.Click += (o, e) => DoThings();
}
}
Because during the constructor call the visual tree for the control is not build up yet. From MSDN OnApplyTemplate:
Attach class-defined event handlers to parts of the template. For
example, you might want class logic to handle KeyDown events from a
TextBox template part so that UI states are updated, and other events
that are specific to your control are raised instead.

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;

WPF Custom Control derived from ItemsControl fails to display bound data

I've created a custom control called MovableItemsControl, inheriting from ItemsControl, in order to override the GetContainerForItemOverride() method. My problem is that none of the objects in the bound collection are displaying. Currently, I'm binding to an OberservableCollection of strings, and I can see that they're in ItemsSource when I look through the debugger.
The custom control is shown below:
public class MovableItemsControl : ItemsControl
{
static MovableItemsControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MovableItemsControl), new FrameworkPropertyMetadata(typeof(MovableItemsControl)));
}
/// <summary>
/// Wraps each content object added to the ItemsControl in a NodeWrapper
/// </summary>
protected override DependencyObject GetContainerForItemOverride()
{
NodeWrapper nodeWrapper = new NodeWrapper();
return nodeWrapper;
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is NodeWrapper;
}
}
NodeWrapper is a UserControl consisting of a custom control derived from Thumb (MoveThumb) and a Label (the Label is just for testing).
<Style TargetType="{x:Type local:MovableItemsControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MovableItemsControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Have you created the default Style for MoveableItemsControl with a ControlTemplate in the Generic.xaml file of the project containing the control? If not, there's nothing for the control to render when it loads.
UPDATE
The ControlTemplate for an ItemsControl needs to contain an ItemsPresenter as a placeholder for the items to be injected (similar to ContentPresenter for ContentControl). Your current template only has an empty Border.
I think you are missing inside your style ControlTemplate Border either:
a) An ItemPresenter (eg <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>) OR
b) A pannel with IsItemsHost set true (eg <StackPanel IsItemsHost="True"/>)

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