DataTemplate / ContentTemplate - exchange controls - wpf

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

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

TextBlock Vs ContentControl in performance in XAML

I have a section header which contains two words to be displayed in XAML with a little bit of style with fontsize and fontfamily. I can implement this with TextBlock or ContentControl (by setting the content property to the header text).
The TextBlock is not derived from Control, so is the intent of TextBlock to perform better in XAML for text compared to a contentcontrol?
Your TextBlock is just that, a TextBlock as framework element when you invoke it is just an object. So when you write <TextBlock Text="Blah Blah Blah"/> that's literally all it is.
When you're using a ContentControl you're actually invoking a templated control that will have multiple elements in its tree that are added for every instance. So example you're using;
<Style TargetType="ContentControl">
<Setter Property="Foreground" Value="#FF000000"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Cursor="{TemplateBinding Cursor}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So a short version to your answer would be Yes, TextBlock will perform better and is suggested to be used over templated controls like ContentControl or Label etc. wherever possible.
Hope this helps, cheers.
To directly answer your question -- if your intent is to only always render text, yes, use only a TextBlock. As others have pointed out using a ContentControl/ContentPresenter and setting to a string will wrap it anyway, and you have increased your element count +1 unnecessarily.

How to build triggers for a WPF custom control

I'm buiding a WPF custom control I'm calling a ResponseTimer. This basically consists of a DispatcherTimer and a ProgressBar. It has properties called TimeElapsed and TimeoutPeriod that are TimeSpans. In the user interface of my program, events occur periodically. There is a time period within which the user can resopnd to the event. If the interval expires, the program takes action on its own.
I want to use this control in two places. One will be on an item that appears in a ListBox, the other at the bottom of a window. For the copes in the ListBox, when the time period has expired, I want to hide the ProgressBar, while I don't want to hide the control at the bottom of the window.
To get this functionality, I've defined two bool DependcyProperties called HideIfExpired and IsExpired. If HideIfExpired is true, then the ProgressBar will be hidden if IsExpired is true. Simple.
I want to use triggers in the default content template for the control in Generic.xaml. I'm not sure how to write the triggers, though.
Here's the XAML for the control:
<Style TargetType="{x:Type local:ResponseTimer}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ResponseTimer}">
<StackPanel Visibility="{TemplateBinding Visibility}">
<ProgressBar Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
FlowDirection="{TemplateBinding FlowDirection}"
Foreground="{TemplateBinding Foreground}"
Height="{TemplateBinding Height}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
IsEnabled="{TemplateBinding IsEnabled}"
IsTabStop="False"
Margin="{TemplateBinding Margin}"
MaxHeight="{TemplateBinding MaxHeight}"
MaxWidth="{TemplateBinding MaxWidth}"
MinHeight="{TemplateBinding MinHeight}"
Minimum="0"
MinWidth="{TemplateBinding MinWidth}"
Name="PART_ProgressBar"
Opacity="{TemplateBinding Opacity}"
OpacityMask="{TemplateBinding OpacityMask}"
Orientation="{TemplateBinding Orientation}"
Padding="{TemplateBinding Padding}"
Panel.ZIndex="{TemplateBinding Panel.ZIndex}"
RenderSize="{TemplateBinding RenderSize}"
RenderTransform="{TemplateBinding RenderTransform}"
RenderTransformOrigin="{TemplateBinding RenderTransformOrigin}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Style="{TemplateBinding Style}"
Tag="{TemplateBinding Tag}"
ToolTip="{TemplateBinding ToolTip}"
UseLayoutRounding="{TemplateBinding UseLayoutRounding}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Visibility="{Binding Path=IsExpired, Converter={StaticResource InvertedBoolToVisibility}, RelativeSource={RelativeSource TemplatedParent}}"
Width="{TemplateBinding Width}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I'm not sure any more why the StackPanel is in there, it may be a remnant of a previous version of the control. I might replace it with a Border, just to have a way to hide the whole thing.
Anyway, how do I write my triggers?
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsExpired" Value="True" />
<Condition Property="HideIfExpired" Value="True" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Visibility" Value="Collapsed" />
</MultiTrigger.Setters>
</MultiTrigger>
</ControlTemplate.Triggers>

How to make a 2 state Image button?

I'm actually surprised that I didn't find the answer to this simple question here. I want to create a simple button with Image content. When pressed the image should change to another one and after the button is released change back to the original image. What is the easiest way to do this?
EDIT: This is not a toggle button!
You can achieve this using a ControlTemplate to specify the visual structure of your button.
The below XAML shows how to add an image to your button (maintaining the standard chrome border though you can remove this if you don't want it), plus adding a trigger on the isPressed event to set your other image. You'll need to include the namespace xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Luna" as well if you want to use the chrome button border.
<Button>
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Microsoft_Windows_Themes:ButtonChrome x:Name="Chrome" BorderBrush="{TemplateBinding BorderBrush}" Fill="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderDefaulted="{TemplateBinding IsDefaulted}" SnapsToDevicePixels="true" ThemeColor="NormalColor">
<Image x:Name="buttonImage" Source="defaultImage"/>
</Microsoft_Windows_Themes:ButtonChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Source" TargetName="buttonImage" Value="onPressedImage"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>

WPF Visual inheritance

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

Resources