Binding style for user control - wpf

This is my xaml code:
<UserControl x:Class="UserControl1"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Style TargetType="TextBox" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style.Resources>
<VisualBrush x:Key="CueBannerBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
<VisualBrush.Visual>
<Label Content="{Binding Path=CueBannerText}" Foreground="LightGray" />
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<TextBox TextWrapping="Wrap"/>
</Grid>
</UserControl>
I want to bind set the Label Content by code if I call the control in my project. The way I do it is like this:
Public Class UserControl1
Public Property CueBannerText As String
Get
Return _oText
End Get
Set(value As String)
_oText = value
End Set
End Property
Private _oText As String = "Search"
Public Sub New()
InitializeComponent()
Me.DataContext = Me
End Sub
End Class
If I call my control in the code With this:
<Window x:Class="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"
xmlns:Noru="clr-namespace:NoruTextBox;assembly=NoruTextBox">
<Grid>
<Noru:UserControl1></Noru:UserControl1>
</Grid>
</Window>
my control won't show 'Search' as the TextBox isn't selected or contains anything.

To do what you want, you'll either need to implement the INotifyPropertyChanged Interface in your UserControl code behind, or declare a DependencyProperty instead:
Public Shared CueBannerTextProperty As DependencyProperty = DependencyProperty.
Register("CueBannerText", GetType(String), GetType(TestView),
New PropertyMetadata("Search"))
Public Property CueBannerText() As String
Get
Return DirectCast(GetValue(CueBannerTextProperty), String)
End Get
Set
SetValue(CueBannerTextProperty, value)
End Set
End Property
Disclaimer: I just converted this to VB using an online converter, so I can't confirm its correctness.
Using a DependencyProperty will also enable you to set this value in a Style, Animation (unlikely in this situation), or a Binding:
<Noru:UserControl1 CueBannerText="{Binding SomeValue}" />

Related

How to bind Enum to Text property of TextBlock in ListBox

I am not successful at getting my DataTrigger to work for binding to enum.
Each line in the ListBox is 'S', just as in the default Setter
XAML:
<Window x:Class="BindToEnumTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BindToEnumTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListBox Grid.Column="0" x:Name="LBMain" ItemsSource="{Binding}" ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Grid.Column="0" x:Name="TxtType">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="S" />
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="TypeEnum.User">
<Setter Property="Text" Value="U"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Code:
namespace BindToEnumTest
{
public enum TypeEnum { None, System, User }
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public Collection<TypeEnum> TypeList = new() { TypeEnum.System, TypeEnum.User, TypeEnum.System, TypeEnum.User };
public MainWindow()
{
InitializeComponent();
LBMain.DataContext = TypeList;
}
}
}
I have tried using 'TypeEnum.User' and 'User' in the DataTrigger - no help.
Using Text="{Binding}" in the TextBlock shows 'User' and 'System' in the ListBox, so it seems to be getting the data.
How can I change this trigger to function?
A TypeEnum instance has no Type property, hence the Binding must not specify a property path:
<DataTrigger Binding="{Binding}" Value="{x:Static local:TypeEnum.User}">
Or with built-in type conversion from string to enum:
<DataTrigger Binding="{Binding}" Value="User">

How to set style of a WPF control according to target?

I have a project that needs to target .NET Framework 3.5 and 4.5, and I want to set property of a WPF control according to the build target. E.g . I have a textblock, and I want its background to be Azure if the build target is 3.5, Cyan if the build target is 4.5 How can I do that?
<Window x:Class="WpfAppMultipleTarget.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAppMultipleTarget"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300">
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="Azure"/> <!-- If target is net framework 3.5 -->
<Setter Property="Background" Value="Cyan"/> <!-- If target is net framework 4.5 -->
</Style>
</Grid.Resources>
<TextBlock>Hello</TextBlock>
</Grid>
You could use the Environment.Version that returns the CLR version. The idea here is to define a DataTrigger in your xaml bound to a boolean, true if the version starts with 4, false otherwise (.net 3.5 has a CLR version that starts with 2), take a look at the CLR versions here.
Your xaml should look something like that:
<....
Title="MainWindow" Height="450" Width="800" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
</Window.Resources>
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAbove4}" Value="True" >
<Setter Property="Background" Value="Cyan"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="Background" Value="Azure"/>
</Style>
</Grid.Resources>
<TextBlock>Hello</TextBlock>
</Grid>
With a property defined in the VM/code behind:
public bool IsAbove4 { get; set; } = Environment.Version.ToString().StartsWith("4");
It's possible to do that with an Interaction.Behaviors you can set this property in XAML included in a style.
Inside the behavior you can read the framework version.
Here is an example.
<Style TargetType="{x:Type TextBox}">
<Style.Setters>
<Setter Property="i:Interaction.Behaviors">
<Setter.Value>
<local:BehaviorName/>
<local:BehaviorName/>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
You can use this code to read the framework version inside the behavior
string version = Assembly.GetExecutingAssembly().GetReferencedAssemblies().Where(c => c.Name.Contains("mscorlib")).FirstOrDefault().Version.ToString();
Hope this helps.

DataContext binding in Page's Resources

I have a page that gets a datacontext objet in the behind code.
I would like to set an empty value to the TextList[11] when the Trigger variable loses the 1 value. The Trigger "int" and the TextList "ObservableCollection" are booth situated in the Datacontext object. The TextList is initialized 20pcs element before set the page datacontext. I have to solve it in wpf code, code behind excluded. My English is pretty poor, sorry!
<Page x:Class="LayerTemplates.Templates.example"
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:LayerTemplates.Templates"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title=""
xmlns:System="clr-namespace:System;assembly=mscorlib">
<Page.Resources>
<DataTemplate x:Key="myDataTemplate" DataType="{x:Type System:String}">
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Trigger}" Value="1">
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<StringAnimationUsingKeyFrames Storyboard.TargetName="Page.DataContext" Storyboard.TargetProperty="TextList[11]" Duration="1">
<DiscreteStringKeyFrame KeyTime="0" Value=""></DiscreteStringKeyFrame>
</StringAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Page.Resources>
<Grid>
...
</Grid>
If you just want to hide a text box or label try to use style triggers instead. As you are setting the value of a string to an empty value I think you might be able to use Visibility="Hidden".
In this example I hide the the label by default but whenever the MyIntProperty becomes 1 I change visibility to Visible.
My xaml code looks like this:
<Window x:Class="TestBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Label Content="{Binding MyTextProperty}">
<Label.Style>
<Style TargetType="Label">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MyIntProperty}" Value="1">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</Grid>
Data binding class and codebehind looks like this:
public class MyViewModel
{
public int MyIntProperty { get; set; } = 1;
public string MyTextProperty { get; set; } = "This is my text";
}
public partial class MainWindow : Window
{
MyViewModel model;
public MainWindow()
{
InitializeComponent();
model = new MyViewModel();
this.DataContext = model;
}
}
Note that in order to get the visibility to update automatically you have let the view model class implement INotifyPropertyChanged for the MyIntProperty.

Mouseover border in a custom control for a textblock

I am trying to create a custom control for a text block that when moused over, a border will appear. I am pretty new to WPF and have only made some very simple custom controls. I need to implement this in a XAML UserControl.
Any suggestions would be greatly appreciated. Thanks again, StackOverflow.
EDIT: I am going to have to bind a persistence property to several different controls, so I really need to do this in a custom control. This is what I have, and it isn't working:
xmlns:customControls="clr-namespace:****.CustomControls"
....
<customControls:MouseOverBorder>
<TextBlock Style="{StaticResource ResourceKey=HomePageButtonText}"
Height="100"
Width="100"
Margin="5"
Text="View Reports" />
</customControls:MouseOverBorder>
And the UserControl:
<UserControl
x:Class="****.MouseOverBorder"
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"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<Style x:Key="MouseOverBorder" TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="3" />
<Style.Triggers>
<Trigger Property="Border.IsMouseOver" Value="true">
<Setter Property="BorderBrush" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<Border Style="{DynamicResource MouseOverBorder}" BorderThickness="1" CornerRadius="3" SnapsToDevicePixels="True"/>
No need to make a UserControl. I've managed to accomplish this with the following markup:
<Border Style="{DynamicResource BorderStyle1}" BorderThickness="1" CornerRadius="3" >
<TextBlock Text="TextBlock" />
</Border>
Here's the style:
<Style x:Key="BorderStyle1" TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="3"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="#FF123BBA"/>
</Trigger>
</Style.Triggers>
</Style>
EDIT:
Still don't get it why do you need a UserControl (please don't call it custom control - these are different things), but let's consider your example.
When you write the following
<customControls:MouseOverBorder>
<TextBlock Style="{StaticResource ResourceKey=HomePageButtonText}"
Height="100"
Width="100"
Margin="5"
Text="View Reports" />
</customControls:MouseOverBorder>
you are actually setting MouseOverBorder.Content property. Originally it's Content is defined in MouseOverBorder.xaml file. So you are replacing all your UserControl structure with TextBlock. But still I got your idea and have solution for it.
First, add custom DependencyProperty and CLR wrapper for it to MouseOverBorder class:
public static readonly DependencyProperty MyContentTemplateProperty =
DependencyProperty.Register("MyContentTemplate", typeof(DataTemplate), typeof(MouseOverBorder), null);
[Browsable(true)]
[Category("Other")]
public DataTemplate MyContentTemplate
{
get { return (DataTemplate)GetValue(MyContentTemplateProperty); }
set { SetValue(MyContentTemplateProperty, value); }
}
Second, make something inside MouseOverBorder use this property, e.g.
<ContentPresenter ContentTemplate="{Binding MyContentTemplate, ElementName=userControl}"/>
<!-- userControl is the Name of MouseOverBorder, defined in xaml -->
At last, you can use your UserControl as following:
<customControls:MouseOverBorder>
<customControls:MouseOverBorder.MyContentTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource ResourceKey=HomePageButtonText}"
Height="100"
Width="100"
Margin="5"
Text="View Reports" />
</DataTemplate>
</customControls:MouseOverBorder.MyContentTemplate>
</customControls:MouseOverBorder>

Set ListBoxItem.IsSelected when child TextBox is Focused

I have a typical MVVM scenario:
I have a ListBox that is binded to a List of StepsViewModels.
I define a DataTemplate so that StepViewModels are rendered as StepViews.
The StepView UserControl have a set of labels and TextBoxs.
What I want to do is to select the ListBoxItem that is wrapping the StepView when a textBox is focused. I've tried to create a style for my TextBoxs with the following trigger:
<Trigger Property="IsFocused" Value="true">
<Setter TargetName="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}" Property="IsSelected" Value="True"/>
</Trigger>
But I get an error telling me that TextBoxs don't have an IsSelected property. I now that but the Target is a ListBoxItem.
How can I make it work?
There is a read-only property IsKeyboardFocusWithin that will be set to true if any child is focused. You can use this to set ListBoxItem.IsSelected in a Trigger:
<ListBox ItemsSource="{Binding SomeCollection}" HorizontalAlignment="Left">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Width="100" Margin="5" Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As Jordan0Day correctly pointed out there can be indeed big problems using IsKeyboardFocusWithin solution. In my case a Button in a Toolbar which regards to the ListBox was also not working anymore. The same problem with focus. When clicking the button the ListBoxItem does loose the Focus and the Button updated its CanExecute method, which resulted in disabling the button just a moment before the button click command should be executed.
For me a much better solution was to use a ItemContainerStyle EventSetter as described in this post: ListboxItem selection when the controls inside are used
XAML:
<Style x:Key="MyItemContainer.Style" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="LightGray"/>
<EventSetter Event="GotKeyboardFocus" Handler="OnListBoxItemContainerFocused" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="backgroundBorder" Background="White">
<ContentPresenter Content="{TemplateBinding Content}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="backgroundBorder" Property="Background" Value="#FFD7E6FC"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
EventHandler in the code behind of the view:
private void OnListBoxItemContainerFocused(object sender, RoutedEventArgs e)
{
(sender as ListBoxItem).IsSelected = true;
}
One way to achieve that is by implementing a custom behavior using an attached property. Basically, the attached property would be applied to the ListBoxItem using a style, and would hook up to their GotFocus event. That even fires if any descendant of the control gets the focus, so it is suitable for this task. In the event handler, IsSelected is set to true.
I wrote up a small example for you:
The Behavior Class:
public class MyBehavior
{
public static bool GetSelectOnDescendantFocus(DependencyObject obj)
{
return (bool)obj.GetValue(SelectOnDescendantFocusProperty);
}
public static void SetSelectOnDescendantFocus(
DependencyObject obj, bool value)
{
obj.SetValue(SelectOnDescendantFocusProperty, value);
}
public static readonly DependencyProperty SelectOnDescendantFocusProperty =
DependencyProperty.RegisterAttached(
"SelectOnDescendantFocus",
typeof(bool),
typeof(MyBehavior),
new UIPropertyMetadata(false, OnSelectOnDescendantFocusChanged));
static void OnSelectOnDescendantFocusChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ListBoxItem lbi = d as ListBoxItem;
if (lbi == null) return;
bool ov = (bool)e.OldValue;
bool nv = (bool)e.NewValue;
if (ov == nv) return;
if (nv)
{
lbi.GotFocus += lbi_GotFocus;
}
else
{
lbi.GotFocus -= lbi_GotFocus;
}
}
static void lbi_GotFocus(object sender, RoutedEventArgs e)
{
ListBoxItem lbi = sender as ListBoxItem;
lbi.IsSelected = true;
}
}
The Window XAML:
<Window x:Class="q2960098.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:q2960098">
<Window.Resources>
<DataTemplate x:Key="UserControlItemTemplate">
<Border BorderBrush="Black" BorderThickness="5" Margin="10">
<my:UserControl1/>
</Border>
</DataTemplate>
<XmlDataProvider x:Key="data">
<x:XData>
<test xmlns="">
<item a1="1" a2="2" a3="3" a4="4">a</item>
<item a1="a" a2="b" a3="c" a4="d">b</item>
<item a1="A" a2="B" a3="C" a4="D">c</item>
</test>
</x:XData>
</XmlDataProvider>
<Style x:Key="MyBehaviorStyle" TargetType="ListBoxItem">
<Setter Property="my:MyBehavior.SelectOnDescendantFocus" Value="True"/>
</Style>
</Window.Resources>
<Grid>
<ListBox ItemTemplate="{StaticResource UserControlItemTemplate}"
ItemsSource="{Binding Source={StaticResource data}, XPath=//item}"
HorizontalContentAlignment="Stretch"
ItemContainerStyle="{StaticResource MyBehaviorStyle}">
</ListBox>
</Grid>
</Window>
The User Control XAML:
<UserControl x:Class="q2960098.UserControl1"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UniformGrid>
<TextBox Margin="10" Text="{Binding XPath=#a1}"/>
<TextBox Margin="10" Text="{Binding XPath=#a2}"/>
<TextBox Margin="10" Text="{Binding XPath=#a3}"/>
<TextBox Margin="10" Text="{Binding XPath=#a4}"/>
</UniformGrid>
</UserControl>
If you create a User Control and then use it as the DataTemplate It seems to work cleaner.
Then you don't have to use the dirty Style Triggers that Don't work 100% of the time.
Edit: Someone else already had the same answer on a different question: https://stackoverflow.com/a/7555852/2484737
Continuing on Maexs' answer, using an EventTrigger instead of an EventSetter removes the need for code-behind:
<Style.Triggers>
<EventTrigger RoutedEvent="GotKeyboardFocus">
<BeginStoryboard>
<Storyboard >
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsSelected" >
<DiscreteBooleanKeyFrame Value="True" KeyTime="0:0:0"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>

Resources