QStackedLayout equivalent in WPF - 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;

Related

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

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

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.

Continue WPF gridview alteration

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.

Resources