WPF Visual inheritance - wpf

Need an advice (better from your real projects) - what is the best way to do visual inheretence in WPF?
More concrete: How to inherete window view with a statusbar?
There is no way to inherete one xaml file from another. Then, are you create User Control MyStatusbar and paste it on every page?
It is possible to create Style Template for base window and use style inheretence, however this only for simple visual properties (color, size).
Second idea is to create base DataTemplate, but there is no inheritance.
P.S. In WinForms there is base Form with status bar and some logic. After adding property
public string StatusbarText {set{baseStatusbar.Text = value;}}
it is very simple to use the property in child forms. Plus we have view inheritance with status bar.
I know how to inherete logic in WPF, but what to do with visualisation.

You could certainly create a custom Window control that adds a StatusbarText property. Alternatively, you could use a custom Style for Window, the only question there is how to pass the status bar items into your Style. For that you can use attached properties.
If you go this route, you cannot inherit your custom Style from the default one, as you need to completely redefine the ControlTemplate. A Style for Window would look like:
<ControlTemplate x:Key="WindowTemplateKey"
TargetType="{x:Type Window}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<AdornerDecorator>
<DockPanel>
<StatusBar DockPanel.Dock="Bottom" ItemsSource="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}" />
<ContentPresenter/>
</DockPanel>
</AdornerDecorator>
<ResizeGrip x:Name="WindowResizeGrip"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Visibility="Collapsed"
IsTabStop="false"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Window.ResizeMode"
Value="CanResizeWithGrip"/>
<Condition Property="Window.WindowState"
Value="Normal"/>
</MultiTrigger.Conditions>
<Setter TargetName="WindowResizeGrip"
Property="Visibility"
Value="Visible"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="{x:Type Window}"
TargetType="{x:Type Window}">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
<Setter Property="Background"
Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<AdornerDecorator>
<DockPanel>
<StatusBar DockPanel.Dock="Bottom" ItemsSource="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}" />
<ContentPresenter/>
</DockPanel>
</AdornerDecorator>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Window.ResizeMode"
Value="CanResizeWithGrip">
<Setter Property="Template"
Value="{StaticResource WindowTemplateKey}"/>
</Trigger>
</Style.Triggers>
</Style>
If you use the Style above, you can set the Window.Tag property to be a list of items you want displayed in the StatusBar. The biggest problem with this approach is you would need to add attached properties for things like StatusBar.ItemContainerStyle so you can customize the look of your status bar.
Same holds for if you use a DataTemplate. So i you know you only ever want single text in your StatusBar, you could use the following in the ControlTemplates above and set the Window.Tag to the string (or use an attached property).
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem Content="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}" />
</StatusBar>

It's better to use MVVM, look WPF Apps With The Model-View-ViewModel Design Pattern
You can write a base ViewModel with StatusbarText property and then inherit from the base ViewModel.
Then you can use this ViewModel property with data binding in Styles and Templates, look Customize Data Display with Data Binding and WPF
Also look at this question

Related

Foreground does not apply for labels

I have some labels like this:
<Label x:Name="ledCalculate" Width="Auto" Content="Calculate" Height="25" FontFamily="Cambria" FontSize="18.667" VerticalContentAlignment="Bottom" Padding="10,0,0,0" BorderThickness="0,0,0,1" Style="{StaticResource RightPanelLabels}" Grid.Row="11"/>
I defined a style for it:
<Style TargetType="Label" x:Key="RightPanelLabels" BasedOn="{StaticResource
{x:Type Label } }">
<Setter Property="Foreground" Value="#FFEABEBE"></Setter>
<Setter Property="BorderBrush" Value="#FF818080"></Setter>
<Setter Property="BorderThickness" Value="0,0,0,1"></Setter>
</Style>
Border thickness setter in style applies for control and it has upper precedence in style, and visual studio ignores its values in local, but foreground setter in style does not applies, when i set it locally it applies,
Why border thickness in style have upper precedence but foreground in local have upper precedence????????
I know there is a default template for labels like bellow that have a trigger for IsEnabled , that template is something like this:
<ControlTemplate TargetType="{x:Type Label}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Can anyone help me?
Sorry for my English.
All the local styles take precedence over defined styles, so inline styles will always override other styles defined in resources.
When I run the code I found that Style's BorderThickness also get overridden by the local inline style value for BorderThickness as well.
I too would love to hear if there's a workaround for this.
I found this article as well, hope it helps Same problem with a style trigger though
EDIT:
Upon trying it few different ways I found using this method we can override the label's inline Foreground style, but I don't know whether it's the healthiest way
<Style TargetType="{x:Type Label}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}"
Foreground="Red" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Label FontSize="50" Foreground="Black"> I am Black inline, Red on Style </Label>
This is me just editing the ContentTemplate of the Label .
Basically, this possible for Label because Label derives from ContentControl whereas TextBlock lives in the System.Windows.Controls namespace, it is not a control. It derives directly from FrameworkElement.
TextBlock are used in many other controls to show texts, including in Label.
Refer this article to know more about this
you have to change property name in style setter
<Setter Property="TextBlock.Foreground" Value="#FFEABEBE">

WPF Textblock Tooltip does not wrap

I am trying to create a tooltip that wraps automatically (and also has an advanced mode that takes normal content, but that's later). Anyway, I'm setting the content as a string and making the content just a textblock with wrapping. However I can't figure out why this isn't working. Here is the style I'm working on:
<Style x:Key="StHelpLinkBase" TargetType="{x:Type graphicElements:MyHelpLink}">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Background" Value="{StaticResource BrHelpLinkBackground}" />
<Setter Property="Width" Value="12" />
<Setter Property="Height" Value="12" />
<Setter Property="Margin" Value="5" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type graphicElements:MyHelpLink}">
<Grid x:Name="templateRoot">
<Image Source="Images/Icon_16_Help.png" Stretch="UniformToFill" MaxHeight="16" MaxWidth="16"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
x:Name="PART_Image">
<Image.ToolTip>
<ToolTip Background="{TemplateBinding Background}" BorderThickness="0"
DataContext="{Binding DataContext, ElementName=PART_Image}"
TextElement.Foreground="{TemplateBinding Foreground}"
ContentTemplate="{StaticResource DtTooltipAdvanced}"
MaxWidth="150"
x:Name="PART_Tooltip">
<ContentPresenter />
</ToolTip>
</Image.ToolTip>
</Image>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here is the basic template referenced:
<DataTemplate x:Key="DtTooltipBasic">
<Grid>
<TextBlock Text="{Binding Content, RelativeSource={RelativeSource AncestorType=ToolTip}}"
TextWrapping="Wrap"
Foreground="White"
Margin="15"
FontFamily="Resources/#Artifakt Element"
FontSize="9pt" />
</Grid>
</DataTemplate>
And here is the usage (MyHelpLink inherits from ContentControl):
<graphicElements:MyHelpLink Content="This is some help text that is long and is just set as straight string in content but it should wrap I hope." />
I've tried setting the MaxWidth on the tooltip as I have it now, I've tried setting it on the Grid that is in the DataTemplate, and I've tried setting it on the textblock itself and all just cut off the text. I also tried setting the Width property of the textblock directly and same thing...
So why doesn't this wrap?
Ok well I still don't know why this didn't work but I ended up with another solution. Through some experimenting I found that if I put the textblock directly inside the control template instead of a data template it worked and wrapped correctly. However in order to switch it I couldn't use it that way.
So what I did was make two control templates; one with a wrapping textblock for generic content and one with ContentPresenter for non-string content. I then made the style with a trigger on the content type (I made a custom readonly dependency property in my class denoting to trigger the change if the content is anything except a string). The trigger changes the template from the wrapping textblock to the content presenter depending on the type of content set.
If anyone knows why it doesn't work inside a DataTemplate I would love to know and will mark as the answer...

Is it possible to create a BulletDecorator using the current theme?

I'm using the well known technique of styling a list box to look like a radio button group.
The style presents a BulletDecorator for each item in the list. In order to do this I need to reference a specific theme assembly such as PresentationFramework.Aero.dll, and then explicitly use that in my style.
xmlns:theme="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
<BulletDecorator.Bullet>
<theme:BulletChrome
Background="{TemplateBinding Control.Background}"
BorderBrush="{TemplateBinding Control.BorderBrush}"
IsRound="True"
RenderMouseOver="{TemplateBinding UIElement.IsMouseOver}"
IsChecked="{TemplateBinding ListBoxItem.IsSelected}" />
</BulletDecorator.Bullet>
Is there a way to create a BulletDecorator which is styled using the current or default theme, so that I don't need to reference an explicit theme?
You don't need to recreate the RadioButton template... why don't you just use a RadioButton in the ItemContainerStyle?
<Style x:Key="RadioButtonGroup" TargetType="ListBox">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<RadioButton IsChecked="{TemplateBinding IsSelected}" Content="{TemplateBinding Content}" GroupName="ListBoxItems" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
Note that there's an issue with this approach: if you use this style for several ListBoxes in the same window, all RadioButtons will be mutually exclusive, because they will share the same GroupName. See this article for a workaround.

DataTemplate / ContentTemplate - exchange controls

How can i solve the following (simplified) problem?
M-V-VM context. I want to show text at the UI.
In case the user has the rights to change the text, i want to use a textbox to manipulate the text.
In case the user has no rights, i want to use a label to only show the text.
My main problem: how to exchange textbox and label and bind Text resp. Content to the same property in viewmodel.
Thanks for your answers
Toni
There are a few ways of achieving this, with varying degrees of ease of reuse. You can have a DataTemplateSelector that could return the appropriate DataTemplate for a given property (depending on how this is written, you may be able to use it for each of your properties).
You could create a DataTemplate for each property, and change visibility based on a DataTrigger (this gets really annoying, as it is a lot of copy and paste).
I think the easiest way of doing this is with a specialized ControlTemplate for the TextBox. Basically, when it is disabled, instead of graying it out, you can just make it look like a TextBlock:
<ControlTemplate x:Key="PermissionedTextBox" TargetType="{x:Type TextBox}">
<Border x:Name="bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="bd" Property="BorderBrush" Value="{x:Null}" />
<Setter TargetName="bd" Property="Background" Value="{x:Null}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Then you can use it like so:
<TextBox Text="{Binding PermissionedText}" IsEnabled="{Binding CanEdit}" />

WPF: Disable ListBox, but enable scrolling

Been banging my head against this all morning.
Basically, I have a listbox, and I want to keep people from changing the selection during a long running process, but allow them to still scroll.
Solution:
All the answers were good, I went with swallowing mouse events since that was the most straight forward. I wired PreviewMouseDown and PreviewMouseUp to a single event, which checked my backgroundWorker.IsBusy, and if it was set the IsHandled property on the event args to true.
If you look in to the control template of the ListBox, there is a ScrollBar and ItemsPresenter inside. So Make the ItemsPresenter Disabled and you will get this easily. Use the bellow Style on the ListBox and you are good to go.
<Style x:Key="disabledListBoxWithScroll" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Border x:Name="Bd" SnapsToDevicePixels="true" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="1">
<ScrollViewer Padding="{TemplateBinding Padding}" Focusable="false">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" IsEnabled="False" IsHitTestVisible="True"/>
</ScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
</Trigger>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
On the ListBox use the Style
<ListBox Style="{DynamicResource disabledListBoxWithScroll}" ..... />
I found that putting a disabled ListBox in a ScrollViewer with auto scrolling enabled gives the desired effect.
The trick is to not really disable. Disabling will lock out all messages from the scroll box.
During the long operation, gray out the text in the list box using its .ForeColor property and swallow all mouse clicks. This will simulate disabling the control and allow scrolling unimpeded.
While it's for Silverlight, maybe this blog post would help you get going in the right direction? Silverlight No Selection ListBox and ViewBox
I used this solution, it's really easy and works perfectly:
For every SurfaceListBoxItem item you put in the Listbox, do this:
item.IsHitTestVisible = false;
This worked best for me. It's easy and whole code is in XAML which is IMO very neat.
<ListBox ItemsSource="{Binding MySource}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsEditing}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsEditing}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Another option worth considering is disabling the ListBoxItems. This can be done by setting the ItemContainerStyle as shown in the following snippet.
<ListBox ItemsSource="{Binding YourCollection}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsEnabled" Value="False" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
If you don't want the text to be grey you can specify the disabled color by adding a brush to the style's resources with the following key: {x:Static SystemColors.GrayTextBrushKey}. The other solution would be to override the ListBoxItem control template.
This question is pretty much the same as this one: There ain't ListBox.SelectionMode=“None”, is there another way to disable selection in a listbox? and my answer is the same.
I found a very simple and straight forward solution working for me, I hope it would do for you as well
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Focusable" Value="False"/>
</Style>
A complete answer using http://www.codeproject.com/Tips/60619/Scrollable-Disabled-ListBox-in-WPF
The Style:
<Style TargetType="{x:Type local:CustomListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomListBox}">
<Border SnapsToDevicePixels="true" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="1">
<ScrollViewer IsEnabled="True">
<ItemsPresenter IsEnabled="{Binding Path=IsEnabledWithScroll, RelativeSource={RelativeSource TemplatedParent}}" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The class
public class CustomListBox : ListBox
{
public bool IsEnabledWithScroll
{
get { return (bool)GetValue(IsEnabledWithScrollProperty); }
set { SetValue(IsEnabledWithScrollProperty, value); }
}
public static readonly DependencyProperty IsEnabledWithScrollProperty =
DependencyProperty.Register("IsEnabledWithScroll", typeof(bool), typeof(CustomListBox), new UIPropertyMetadata(true));
}
Then instead of setted IsEnabled on the ListBox, use IsEnabledWithScroll instead. Scrolling will work if the listbox is enabled or disabled.
There seem to be many ways to skin this particular cat. I found that by setting IsHitTestVisible on the ItemsContainerStyle in XAML I got exactly what I needed:
<ListBox IsHitTestVisible="true" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsHitTestVisible" Value="False" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Well, I found a sweet way to provide this feature. What I did is that in the DataTemplate of the listBox I binded the parent layout enable property with the boolean flag using Page as Source.
Step 1 - Provide the x:Name attribute to the page. If the page you are using is extended with base page than make sure that the base page is not an abstract class and has an default constructor without any arguments.
<Page x:Class="OPMS.Views.Registration"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Name="MainPage"
d:DesignWidth="1024"
Title="Registration"
>
Step 2 - Use the Page as a source for the DataTemplate parent layout items IsEnabled property
<ListBox Grid.Row="2"
ItemsSource="{Binding TestGroups}"
AlternationCount="2"
Padding="0"
Margin="10,5,10,10"
>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}"
IsChecked="{Binding IsSelected}"
IsEnabled="{Binding Source={x:Reference MainPage}, Path=DataContext.BindingVariableHere}"
/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Resources