WPF Databinding in ControlTemplate not working C# - c#

I try to bind a simple bool to a checkbox and a datatrigger in a controltemplate, but nothing is working. The checkbox is only for test purpose. I already went through the posts here, but most problems are caused by setting a property directly and not in the style, thus overwriting it. The datatrigger using the mouseover property is working just fine. Its only the binding to the bool which doesn't work.
My Code so far:
cs:
public partial class HostFrame : UserControl
{
public bool test { get; set; }
public HostFrame()
{
test = true;
InitializeComponent();
}
}
Xaml:
<UserControl x:Class="OwnDrawingv2.Elements.HostFrame"
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"
xmlns:local="clr-namespace:OwnDrawingv2.Elements"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Style TargetType="{x:Type local:HostFrame}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:HostFrame}">
<Grid Background="LightBlue" Name="host_grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<ContentPresenter Name="content" Grid.Row="1" Grid.Column="1" />
<CheckBox Grid.Column="0" Grid.Row="0" IsChecked="{Binding Path=test}"> <!--Test prupose-->
</CheckBox>
<Image Grid.Column="1" Grid.Row="0" Name="attention" Width="30" Height="30" Source="/attention_icon.png">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Visibility" Value="Visible"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=test}" Value="true">
<Setter Property="Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Ellipse Grid.Column="0" Grid.Row="1" Width="10" Height="10" Fill="Black">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Visibility" Value="Hidden"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=content, Path=IsMouseOver}" Value="true">
<Setter Property="Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<Ellipse Grid.Column="2" Grid.Row="1" Width="10" Height="10" Fill="Black">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Visibility" Value="Hidden"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=content, Path=IsMouseOver}" Value="true">
<Setter Property="Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
</UserControl>
UPDATE: I got an answer for my original question and I understand this. But why are there tutorials in the web, which seem to work without the relativesource?
UPDATE2: I missed one answers information about Datacontext, thank you. The problem is solved.

You have not set a Datacontext for your UserControl therefore i assume it will be null. You should see the binding error in your output.
On the other hand, your property test doesnt't notify changes. You should declare it as a dependency property if it belongs to the UserControl (instead of being part of the ViewModel)

if you do the following IsChecked="{Binding Path=test}" that means your UserControl class is the DataContext of it self (or you didn't say that).
So you have 2 solutions (I guess)
1- Move your Test property to your ViewModel and do your Binding.
2- Use RelativeResource and FindAncestor method ({Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}})
PS: IF your HostFrame inherits from Control,UIELement..., It would be better to define a DependencyProperty and do a TemplateBinding

Related

wpf ResourceDictionary not applied to ContentControl

I have a wpf application, here i've made a contentcontroller that has a label and a button. this will be used as an overlay when something is loading.
the .cs file
namespace VLC.WPF.Controls
{
public class LoadingOverlay : ContentControl
{
}
}
the .xaml file
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VLC.WPF.Controls">
<Style TargetType="local:LoadingOverlay">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid Background="Black" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Opacity="0.8">
<ContentPresenter Content="{Binding OverlayContent}"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
all of this will be used like this
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/../Controls/LoadingOverlay.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<controls:LoadingOverlay>
<controls:LoadingOverlay.Resources>
<Style TargetType="controls:LoadingOverlay">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=RefreshNotifyTask.IsCompleted}" Value="True">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</controls:LoadingOverlay.Resources>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Foreground="White" FontSize="20" Text="Artikelen vernieuwen ..." />
<Button x:Name="Cancel" Content="Annuleren"/>
</StackPanel>
</controls:LoadingOverlay>
in various usercontrols, it's functioning correctly but the styly doesn't appear to load.
What could be wrong here? the code looks alright so i think it should be loading but it isn't.
in your style try adding the TargetType to your ControlTemplate like this
<ControlTemplate TargetType="local:LoadingOverlay">
What I don't understand is why are you setting the styling in the UserControl Resources and not using Keys to reference styles in your ResourceDictionary. It would keep your overall code much cleaner and you wouldn't have to change each control if you have to make a minor change later... for example:
<Style x:Key="overlayOne" TargetType="local:LoadingOverlay">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:LoadingOverlay">
<Grid>
<Grid Background="Black" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Opacity="0.8">
<ContentPresenter Content="{Binding OverlayContent}"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=RefreshNotifyTask.IsCompleted, RelativeSource={RelativeSource Self}} Value="True">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
And when you call the control in your page
<local:LoadingOverlay Style="{DynamicResource overlayOne}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Foreground="White" FontSize="20" Text="Artikelen vernieuwen ..." />
<Button x:Name="Cancel" Content="Annuleren"/>
</StackPanel>
</local:LoadingOverlay>
and if you find you need to alter the style for another page, instead of doing an inline style for the control - after the originally defined style try this:
<Style x:Key="overlayTwo" TargetType="local:LoadingOverlay" BasedOn="{StaticResource overlayOne}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=RefreshNotifyTask.IsCompleted, RelativeSource={RelativeSource Self}} Value="True">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
This style uses all the information you have already defined and adds another data trigger, or you could override what is there, change other elements in style such the font size or colors.
Then, you just have to use this key when defining your control
<local:LoadingOverlay Style="{DynamicResource overlayTwo}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Foreground="White" FontSize="20" Text="Artikelen vernieuwen ..." />
<Button x:Name="Cancel" Content="Annuleren"/>
</StackPanel>
</local:LoadingOverlay>
Sorry for the long winded answer, but I see this being a potential problem if you have a lot of these controls on different pages and by not keeping all of your styling in the ResourceDictionary
P.S. If your content is going to be the same that could also be part of the style like below
<Style x:Key="overlayOne" TargetType="local:LoadingOverlay">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:LoadingOverlay">
<Grid>
<Grid Background="Black" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Opacity="0.8">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Foreground="White" FontSize="20" Text="Artikelen vernieuwen ..." />
<Button x:Name="Cancel" Content="Annuleren"/>
</StackPanel>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=RefreshNotifyTask.IsCompleted, RelativeSource={RelativeSource Self}} Value="True">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
And then you only need on your page
<local:LoadingOverlay Style="{DynamicResource overlayOne}"/>
You actually override the style within the LoadingOverlay resources.
Replace <Style TargetType="controls:LoadingOverlay"> by the following <Style TargetType="{x:Type controls:LoadingOverlay}" BasedOn="{StaticResource {x:Type controls:LoadingOverlay}}"> and voila!
The resource must be defined before the UI element that will be using it. If a control uses a style resource, that style must be higher in the visual tree.
Move the style to the UserControl resources
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/../Controls/LoadingOverlay.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="controls:LoadingOverlay">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=RefreshNotifyTask.IsCompleted}" Value="True">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<controls:LoadingOverlay>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Foreground="White" FontSize="20" Text="Artikelen vernieuwen ..." />
<Button x:Name="Cancel" Content="Annuleren"/>
</StackPanel>
</controls:LoadingOverlay>

Style control within control on Trigger

I have a LabeledTextBox in my WPF app that's as simple as can be:
<Grid x:Name="root">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="{Binding Label}"
FontWeight="Bold"
VerticalAlignment="Bottom"
Margin="5,2,5,0"/>
<TextBox Grid.Row="1"
Text="{Binding Text}"
VerticalAlignment="Top"
Margin="5,0,5,2"/>
</Grid>
I bind all my models to that guy to display. I've successfully implemented IDataErrorInfo, and can style the LabeledTextBox like:
<Style TargetType="controls:LabeledTextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="5"/>
</Trigger>
</Style.Triggers>
</Style>
This all works, and the entire control is bordered in red (obviously). What's I'd like is to just manipulate the TextBox within the LabeledTextBox, my end goal being to change the background to a pastel red color.
How can I access my TextBox from within the trigger, when the trigger is set on the entire LabeledTextbox?
I imagine this is a seemingly simple task, I just can't get the syntax right. I'm working in a .NET4.0 environment, if that is relevant.
Thanks!
Hi I dont think we can access elements through styles but yes we can refer through ControlTemplate.Triggers and specifying the TargetName Property in Setter.
<Style TargetType="{x:Type wpfApplication4:LabelledTextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type wpfApplication4:LabelledTextBox}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Margin="5,2,5,0"
VerticalAlignment="Bottom"
FontWeight="Bold"
Text="ergergergegr" />
<TextBox
x:Name="MyTextBox"
Grid.Row="1"
Margin="5,0,5,2"
VerticalAlignment="Top"
Text="gtwererggerg" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="MyTextBox" Property="BorderBrush" Value="Red"/>
<Setter TargetName="MyTextBox" Property="BorderThickness" Value="5"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Add a dependency property to your user control.
Bind Your TextBlock Background property to that dependency property.
Change that property in the trigger.

Hide and show a usercontrol placed inside a usercontrol

I have two user controls(A & B) placed on a particular user control(C). I want these control to come up or show only when button is clicked. I bound the visibility of A and B controls to properties but of no use. I applied styles also to hide/show but again no success.
Also I want usercontrol C to be stretched if in case both A and B controls are not visible.
Here is the usercontrol xaml where main datacontext is RunViewModelObject
<UserControl>
<UserControl.Resources>
<Style TargetType="{x:Type Control}" x:Key="RTMLOVPanel">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding LovPaneVisible, ElementName=RtmLovView, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type Control}" x:Key="AttachmentPanel">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding AttachmentsPaneVisible, ElementName=AttachmentsView, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="RtmLovTemplate" x:Name="RtmLovView">
<Shared:SidePaneView_RtmLov/>
</ControlTemplate>
<ControlTemplate x:Key="AttachmentsTemplate" x:Name="AttachmentsView">
<Shared:SidePaneView_Attachments/>
</ControlTemplate>
</UserControl.Resources>
<Grid DataContext="{Binding RunViewModelObject}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ContentControl Grid.Column="0" Template="{StaticResource ResourceKey=RtmLovTemplate}"
DataContext="{Binding LovObject}" Style="{DynamicResource RTMLOVPanel}" />
<ContentControl Grid.Column="0" Template="{StaticResource ResourceKey=AttachmentsTemplate}"
DataContext="{Binding AttachmentsObject}" Style="{DynamicResource AttachmentPanel}" />
<Grid Grid.Column="1" >
<ContentControl x:Name="CCRunView" Loaded="UserControl_Loaded" Unloaded="UserControl_Unloaded" >
<ContentControl.Content>
<UC:UCDynamicRunForm Visibility="{Binding DataSourceControlVisibility, Converter={StaticResource ResourceKey=BoolToOppositeVisibilityConverter}}" DataContext="{Binding UCDynamicFormVMObject}"/>
</ContentControl.Content>
</ContentControl>
</Grid>
</Grid>
</UserControl>

Landscape-Portrait orientation in WPF

I am new to WPF and working on dynamic view creation. I have a scenario where i need to modify my UI based on monitor landscape and/or portrait, like
I already have property which tells me that monitor is in landscape or portrait mode.
Is this possible in WPF?
This is possible. You would create a view that implements both layouts and switches between them using a DataTrigger:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content">
<Setter.Value>
<!-- Put your portrait layout here -->
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsLandscape}" Value="True">
<Setter Property="Content">
<Setter.Value>
<!-- Put your landscape layout here -->
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Using the {Binding IsLandscape} expression, the DataTrigger observes the IsLandscape property of the view's DataContext. This is explained in detail on MSDN. This means that you should set the view's DataContext property to the object that has the IsLandscape property that you've mentioned in your question. Full example:
Create new empty WPF project.
Update your MainWindow.xaml.cs:
public MainWindow()
{
this.InitializeComponent();
this.DataContext = this; // You would put a ViewModel here when using MVVM design pattern
}
public static readonly DependencyProperty IsLandscapeProperty
= DependencyProperty.Register("IsLandscape",
typeof (bool),
typeof (MainWindow));
public bool IsLandscape
{
get { return (bool) GetValue(IsLandscapeProperty); }
set { SetValue(IsLandscapeProperty, value); }
}
private void ChangeOrientation(object sender, RoutedEventArgs e)
{
this.IsLandscape = !this.IsLandscape;
}
Update your MainWindow.xaml. Delete the default Grid and put this instead:
<Window.Resources>
<Style TargetType="UserControl">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Background" Value="#CCDDEE" />
<Setter Property="Margin" Value="3" />
</Style>
</Window.Resources>
<DockPanel>
<Button DockPanel.Dock="Bottom" Margin="5" Content="Change Orientation"
Click="ChangeOrientation" />
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content">
<Setter.Value>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<UserControl Content="Sub 1" />
<UserControl Grid.Column="1" Content="Sub 2" />
<UserControl Grid.Row="1" Grid.ColumnSpan="2" Content="Main" />
</Grid>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsLandscape}" Value="True">
<Setter Property="Content">
<Setter.Value>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<UserControl Grid.Column="1" Content="Sub 1" />
<UserControl Grid.Column="1" Grid.Row="1" Content="Sub 2" />
<UserControl Grid.RowSpan="2" Content="Main" />
</Grid>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DockPanel>
Yes, you can implement both of this UI and use VisualStateManager to control which UI to display.
Also you can bind visibility of layout container to your property using converter

changing background color of togglebutton when checked

I am trying the distinguish the state of the toggle button when clicked. I have the snippet below
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="OnOffToggleImageStyle" TargetType="ToggleButton">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="DimGray"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ToggleButton Height="60" Content="Text" Style="{StaticResource OnOffToggleImageStyle}">
</ToggleButton>
</Grid>
</Window>
However this does not work when IsChecked value is set to "True" in the style. When set to false it works.
I wonder why. Any answers!
When running your code sample, it appears the style is conflicting with the 'chrome' of the ToggleButton (i.e. the original style of the button).
It would probably be better in this situation to override the template of the ToggleButton to behave in the manner you desire. An ugly example can be found below:
<Style x:Key="myToggleButton" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border x:Name="outer"
BorderBrush="White"
BorderThickness="2"
Opacity=".9"
Background="Transparent">
<Border x:Name="inner"
Margin="8"
BorderThickness="0"
Background="{
Binding Background,
RelativeSource={RelativeSource TemplatedParent}}">
<Grid x:Name="container">
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock x:Name="display"
Grid.Row="1"
Text="{Binding Content, RelativeSource={
RelativeSource TemplatedParent}}"
Foreground="White"
FontSize="11"
FontFamily="Segoe UI"
FontStyle="Normal"
FontWeight="Normal"
Margin="8,0,0,4"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"/>
</Grid>
</Border>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter TargetName="outer" Property="Background" Value="LightBlue"/>
<Setter TargetName="outer" Property="BorderBrush" Value="Transparent"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Categories