Set focus to content of ContentPresenter - wpf

I need to set focus to the content of a ContentPresenter. I can assume the ContentTemplate contains an IInputElement but not anything else about it.
Here is a much simplified example that illustrates the problem:
Main window:
<Window x:Class="FiedControlTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:Esatto.Wpf.CustomControls;assembly=Esatto.Wpf.CustomControls"
xmlns:local="clr-namespace:FiedControlTest">
<Window.Resources>
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Margin" Value="5"/>
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Background" Value="LightBlue"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Path=Options}" Name="cbOptions" DisplayMemberPath="Description"/>
<Button Content="Set focus" Click="SetFocus"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="TextBox:"/>
<TextBox Name="tbText" Text="A bare text box."/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="ContentPresenter:"/>
<ContentPresenter Content="TextBox in a ContentPresenter" Name="cpText">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<TextBox Text="{Binding Mode=OneWay}" IsReadOnly="True"/>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</StackPanel>
</StackPanel>
</Window>
Codebehind:
public partial class MainWindow : Window
{
public MainWindow()
{
this.DataContext = this;
Options = new ObservableCollection<Option>(new[]{
new Option(){TargetType=typeof(TextBox), Description="Bare Text Box"},
new Option(){TargetType=typeof(ContentPresenter), Description="Content Presenter"}
});
InitializeComponent();
cbOptions.SelectedIndex = 0;
}
private void SetFocus(object sender, RoutedEventArgs e)
{
var opt = cbOptions.SelectedItem as Option;
if (opt.TargetType == typeof(TextBox))
tbText.Focus();
if (opt.TargetType == typeof(ContentPresenter))
cpText.Focus();
}
public ObservableCollection<Option> Options { get; set; }
public class Option
{
public Type TargetType { get; set; }
public string Description { get; set; }
}
}
There's not much there. The bare TextBox takes focus as expected; the TextBox presented by the ContentPresenter does not.
I have tried adding Focusable="True" to the ContentPresenter but it doesn't have any visible effect. I've tried doing using Keyboard.SetFocus instead of UIElement.Focus but the behavior doesn't change.
How is this done?

In fact what you set focus is the ContentPresenter, not the inner TextBox. So you can use VisualTreeHelper to find the child visual element (the TextBox in this case) and set focus for it. However with IsReadOnly being true, you won't see any caret blinking (which may also be what you want). To show it in readonly mode, we can just set IsReadOnlyCaretVisible to true:
private void SetFocus(object sender, RoutedEventArgs e)
{
var opt = cbOptions.SelectedItem as Option;
if (opt.TargetType == typeof(TextBox))
tbText.Focus();
if (opt.TargetType == typeof(ContentPresenter)) {
var child = VisualTreeHelper.GetChild(cpText, 0) as TextBox;
if(child != null) child.Focus();
}
}
Here the edited XAML code with IsReadOnlyCaretVisible added:
<TextBox Text="{Binding Mode=OneWay}" IsReadOnly="True"
IsReadOnlyCaretVisible="True"/>
Note that the above code can only be applied in your specific case where you use a TextBox as the root visual of ContentTemplate of a ContentPresenter. In general case, you will need some recursive method to find the child visual (based on VisualTreeHelper), you can search more for this, I don't want to include it here because it's a very well-known problem/code in WPF to find visual child in WPF. (the italic phrase can be used as keywords to search for more).

Related

Handle Events in Custom Control Code Behind

Ok, that probably is a pretty dumb question but I have searched quite a while but could not find a solution for this that works...
I have a Custom control inherited from Control, which shall include code behind automation.
For examble select all text of a controls TextBox when selected, or generate a list of close matches when the content of that TextBox is changed.
public class vokDataGridEdit : Control
{
static vokDataGridEdit()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(vokDataGridEdit), new FrameworkPropertyMetadata(typeof(vokDataGridEdit)));
// Events internal to control (??? found on some how-to's)
EventManager.RegisterClassHandler(typeof(vokDataGridEdit), UIElement.GotKeyboardFocusEvent, new RoutedEventHandler(OnSelectContent), true);
}
// Dependecy Properties ...
// The Event that shall Fire when the TextBox gets Focus / Editing Mode
public static void SelectContent(object sender, RoutedEventArgs e)
{
if (sender is TextBox tb)
{
tb.SelectAll();
}
}
}
And the controls Style Template:
<ResourceDictionary xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ccont = "clr-namespace:App.Controls">
<!-- Default style for the Validation Buttons -->
<Style TargetType="{x:Type ccont:vokDataGridEdit}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ccont:vokDataGridEdit}">
<TextBox Text = "{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=ccont:vokDataGridEdit}}"
BorderThickness = "0"
ContextMenuService.Placement = "Right"
ContextMenuService.PlacementTarget = "{Binding Path=., RelativeSource={RelativeSource Self}}"
GotKeyboardFocus = "SelectContent">
<TextBox.ContextMenu>
<ContextMenu>
<ContextMenu.Template>
<ControlTemplate>
<Border CornerRadius = "5"
Background = "LightGray"
BorderThickness = "1"
BorderBrush = "Gray"
Padding = "2">
<StackPanel Orientation="Vertical">
<!-- Title -->
<TextBlock Text="Test" />
<!-- TODO: List of matches -->
<TextBox Text = "{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=ccont:vokDataGridEdit}}"
BorderThickness = "0" />
</StackPanel>
</Border>
</ControlTemplate>
</ContextMenu.Template>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Question 1: How can I bind the event SelectContent (to select all TextBox content when it get focus, nb: it is part of a DataGrid for the CellEditingTemplate) to GotKeyboardFocus? Events are normaly fine in the Apps code, but for the Custom Control they do not work as there is no "Code Behind" really for the Style...
Question 2: Assuming I have a dependency Property containing an array of words. Based on the content of the TextBox, I would like to select a few words from the Array in the Dependency Property and pass them to a ListBox in the Custom Control (the Content of the ListBox shall only be managed by the Custom Control, not by anyone using that control. Is there a prefered/canonical MVVM schema on how to implement this?
Usually you should post only one question, not multiple. Regarding first one you can use EventSetter e.g. in implicit Style in UserControl's resources:
<Style TargetType="TextBox">
<EventSetter Event="GotKeyboardFocus" Handler="SelectContent"/>
</Style>
Regarding second question - implement a property, which is subset of your list and do update it accordingly e.g. if dependency property was changed(see property changed callback) or some another values were changed which the subset depends on.
Alternatively you could use a behavior for the TextBox and handle events you need there. See e.g. select all behavior:
public class SelectAllBehavior : Behavior<TextBox>
{
private bool _doSelectAll = false;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.GotFocus += AssociatedObject_GotFocus;
AssociatedObject.PreviewMouseUp += AssociatedObject_MouseUp;
AssociatedObject.PreviewMouseDown += AssociatedObject_MouseDown;
}
private void AssociatedObject_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (_doSelectAll)
{
AssociatedObject.Dispatcher.BeginInvoke((Action) (()=>{ AssociatedObject.SelectAll(); }));
}
_doSelectAll = false;
}
private void AssociatedObject_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_doSelectAll = !AssociatedObject.IsFocused;
}
private void AssociatedObject_GotFocus(object sender, System.Windows.RoutedEventArgs e)
{
AssociatedObject.SelectAll();
}
protected override void OnDetaching()
{
AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
AssociatedObject.PreviewMouseUp -= AssociatedObject_MouseUp;
AssociatedObject.PreviewMouseDown -= AssociatedObject_MouseDown;
base.OnDetaching();
}
}
Using this behavior in XAML:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<TextBox Text="Some text">
<i:Interaction.Behaviors>
<local:SelectAllBehavior/>
</i:Interaction.Behaviors>
</TextBox>
Partial Solution:
Finaly I got event on the direct controls to work (controls in a ContextMenu still don't get EventHandlers...).
Apparently the point was using GetTemplateChild() in order to get the TextBox by name, and then associate the Event handlers:
<ResourceDictionary xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ccont = "clr-namespace:App.Controls">
<!-- Default style for the Validation Buttons -->
<Style TargetType="{x:Type ccont:vokDataGridEdit}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ccont:vokDataGridEdit}">
<TextBox Text = "{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=ccont:vokDataGridEdit}}"
BorderThickness = "0"
ContextMenuService.Placement = "Right"
ContextMenuService.PlacementTarget = "{Binding Path=., RelativeSource={RelativeSource Self}}"
x:Name = "TextBox">
<TextBox.ContextMenu>
<ContextMenu x:Name="Menu">
<ContextMenu.Template>
<ControlTemplate>
<Border CornerRadius = "5"
Background = "LightGray"
BorderThickness = "1"
BorderBrush = "Gray"
Padding = "2">
<StackPanel Orientation="Vertical">
<!-- Title -->
<TextBlock Text="Test" x:Name = "Test" />
<!-- TODO: List of matches -->
<TextBox Text = "{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=ccont:vokDataGridEdit}}"
BorderThickness = "0" />
</StackPanel>
</Border>
</ControlTemplate>
</ContextMenu.Template>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And Code (Dependency Properties not shown):
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace App.Controls
{
/// <summary>
/// DataGrid Edit control (see: https://www.c-sharpcorner.com/article/wpf-routed-events/ for RoutedEvents)
/// </summary>
public class vokDataGridEdit : Control
{
static vokDataGridEdit()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(vokDataGridEdit), new FrameworkPropertyMetadata(typeof(vokDataGridEdit)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Demo purpose only, check for previous instances and remove the handler first
if (this.GetTemplateChild("TextBox") is TextBox button)
{
button.PreviewMouseLeftButtonDown += this.SelectContentPreparation;
button.GotKeyboardFocus += this.SelectContent;
button.MouseDoubleClick += this.SelectContent;
//button.GotFocus += this.SelectContent;
}
}
/// <summary>
/// Prepare the Control to ensure it has focus before subsequent event fire
/// </summary>
private void SelectContentPreparation(object sender, MouseButtonEventArgs e)
{
if (sender is TextBox tb)
{
if (!tb.IsKeyboardFocusWithin)
{
e.Handled = true;
tb.Focus();
}
}
}
private void SelectContent(object sender, RoutedEventArgs e)
{
if (sender is TextBox tb)
{
e.Handled = true;
tb.SelectAll();
}
}
}
}

WPF - Selector - Custom Control - SelectionChanged Event Not Firing

I am new to custom control creation. I have laid some groundwork for a new custom control based on the Selector class. My understanding was that I should use this class since I needed the control to have an Items collection and the ability to handle selections. I believe that changing the ItemTemplate may have overriden some of this ability because I do not receive the SelectionChanged event at the control level or application level. I would think if I'm right that there is some sort of SelectionRegion XAML tag that I can put the DataTemplate innards into. I have not had luck in finding anything like this. After looking through Google for a while, I am ready to just ask. What am I missing? Below is the ItemTemplate markup. Thanks for any help. Thanks even more if you can tell me why the Text in TextBlock is enclosed in parentheses even though the data isn't.
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding}" Foreground="Black" Background="White" MinHeight="12" MinWidth="50"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
At the request of a commenter, here is the complete XAML for the control so far:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SourceMedicalWPFCustomControlLibrary">
<Style TargetType="{x:Type local:MultiStateSelectionGrid}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding Code}" Foreground="Black" Background="White" MinHeight="12" MinWidth="50" Padding="2" ToolTip="{Binding Description}"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MultiStateSelectionGrid}">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,0,0,0" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,0,0,0" Content="{TemplateBinding Content}"/>
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And the anemic code-behind as well:
namespace SourceMedicalWPFCustomControlLibrary
{
public class MultiStateSelectionGridState
{
public Brush Background { get; set; }
public Brush Foreground { get; set; }
public Brush Border { get; set; }
public string Text { get; set; }
public MultiStateSelectionGridState()
{
Background = Brushes.White;
Foreground = Brushes.Black;
Border = Brushes.Black;
Text = String.Empty;
}
};
public class MultiStateSelectionGrid : Selector
{
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(object), typeof(MultiStateSelectionGrid),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsParentMeasure));
public object Content
{
get { return (object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty StatesProperty =
DependencyProperty.Register("States", typeof(List<MultiStateSelectionGridState>), typeof(MultiStateSelectionGrid),
new FrameworkPropertyMetadata(new List<MultiStateSelectionGridState>(),
FrameworkPropertyMetadataOptions.AffectsRender));
public List<MultiStateSelectionGridState> States
{
get { return (List<MultiStateSelectionGridState>)GetValue(StatesProperty); }
set { SetValue(StatesProperty, value); }
}
static MultiStateSelectionGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStateSelectionGrid), new FrameworkPropertyMetadata(typeof(MultiStateSelectionGrid)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.SelectionChanged += new SelectionChangedEventHandler(MultiStateSelectionGrid_SelectionChanged);
}
void MultiStateSelectionGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show("Hi");
}
}
}
here is what I do. I use the apply template function of the custom control and add a handlerto the selection chnaged event of the control I want.
simple sample here:
public event EventHandler<SelectionChangedEventArgs> YourControlSelectionChanged;
private void Selector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListSelectionChanged != null) {
ListSelectionChanged(sender, e);
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//find or declare your control here, the x:name in xaml should be YourControl
YourControl== this.Template.FindName("YourControl", this) as YourControlType
YourControl.SelectionChanged += ResultListBox_SelectionChanged;
}
you can then bind to the name of the public event (YourControlSelectionChanged) you declared in your custom control class in xaml.
hope this helps.
From reading some full code examples of different controls, I believe my answer is that I am doing this all wrong. Instead, I need to have control that has a Selector like a ListBox in the ControlTemplate. THEN, #JKing 's advice would help me get to where I need to be. The answer to the actual question asked though is the aforementioned change from using Selector as a base class to having a selector in the template for the control. Thanks for the help.

XAML Style, Setting Behavior Property on DataTrigger

So, I'm new to WPF, so maybe this is trivial but I can't figure it out.
I have a textbox.
<TextBox Text="{Binding NewRateAdjustment.Amount, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True,ValidatesOnExceptions=True}" Style="{StaticResource SurchargeAmountTextBox}" AttachedProperties:TextRules.TextRule ="{StaticResource numericRule}">
<i:Interaction.Behaviors>
<gl:NumericTextBoxBehavior DecimalLimit="2" />
</i:Interaction.Behaviors>
</TextBox>
Now, I need to change the DecimalLimit based upon the choice in a drop down on the page, so I created this Style.
<Style x:Key="SurchargeAmountTextBox" TargetType="{x:Type TextBox}" BasedOn="{StaticResource DefaultTextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=NewRateAdjustment.SelectedRateAdjustment.CalculationMethod.Name, UpdateSourceTrigger=PropertyChanged}" Value="Fuel">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=NewRateAdjustment.SelectedRateAdjustment.CalculationMethod.Name, UpdateSourceTrigger=PropertyChanged}" Value="">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
It seems to work for the colors. But how do write the Property Setter for the DecimalLimit???
You can't change a behavior property through a style, but you can try to apply the behavior through a style. The subject has been aborded in other questions, like this, but in your particular case, you want not only to apply the behavior through a style, but apply it with a different configuration depending on the data.
In the following approach I will use a attached property to accomplish that.
First, a dummy behavior similar to the one you are using:
public class NumericTextBoxBehavior : Behavior<TextBox>
{
public double DecimalLimit { get; set; }
protected override void OnAttached()
{
base.OnAttached();
// Dummy action so we can see the change when its applied
this.AssociatedObject.Text = this.DecimalLimit.ToString();
}
}
Now we create an attached property which is gonna be responsible for applying the behavior (you can do this in another class, or in the behavior class if you have access to it):
public static class NumericTextBoxBehaviorExtension
{
public static double? GetDecimalLimit(DependencyObject obj)
{
return (double?)obj.GetValue(DecimalLimitProperty);
}
public static void SetDecimalLimit(DependencyObject obj, double? value)
{
obj.SetValue(DecimalLimitProperty, value);
}
public static readonly DependencyProperty DecimalLimitProperty =
DependencyProperty.RegisterAttached("DecimalLimit", typeof(double?), typeof(NumericTextBoxBehaviorExtension), new PropertyMetadata(null, OnDecimalLimitChanged));
private static void OnDecimalLimitChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var behaviors = Interaction.GetBehaviors(sender);
// Remove the existing behavior instances
foreach (var old in behaviors.OfType<NumericTextBoxBehavior>().ToArray())
behaviors.Remove(old);
if (args.NewValue != null)
{
// Creates a new behavior and attaches to the target
var behavior = new NumericTextBoxBehavior { DecimalLimit = (double)args.NewValue };
// Apply the behavior
behaviors.Add(behavior);
}
}
}
Finally, the following test case will emulate your scenario. We have a TextBox style which is gonna apply a different DecimalLimit depending on the state of the TextBox's DataContext. The xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Name}" Value="Fuel">
<Setter Property="local:NumericTextBoxBehaviorExtension.DecimalLimit" Value="10.0"/>
</DataTrigger>
<DataTrigger Binding="{Binding Name}" Value="">
<Setter Property="local:NumericTextBoxBehaviorExtension.DecimalLimit" Value="1000.0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="81,1,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Style="{StaticResource TextBoxStyle}"/>
</Grid>
In the code behind, we will make the button's action swap the TextBox's DataContext to verify that the style will update the behavior correctly:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
var target = this.textBox1.DataContext as Target;
if (this.textBox1.DataContext == null || string.IsNullOrEmpty(target.Name))
{
this.textBox1.DataContext = new Target() { Name = "Fuel" };
}
else
{
this.textBox1.DataContext = new Target() { Name = "" };
}
}
}
As you can see, the TextBox's Text will change every time we swap the DataContext, which means the style is indeed aplying the correct behavior.

Change style of TextBlock for TargetNullValue

I want to change the Style of a TextBlock if the value of the bound property is null. I have specified a value for TargetNullValue of the TextBlock to be displayed, but i want to display it with an alternativ style. How can i do it.
My current solution is to use two TextBlocks and control the Visibility of both, to toggle between original and alternativ style. But this solution is not viable, cause i need to duplicate each TextBlock, for displaying alternativ version.
Current Solution:
<TextBlock Visibility="{Binding MyText, Converter={StaticResource nullToVisibilityConverter}}"
FontSize="20"
Foreground="Black"
Text="{Binding MyText}" />
<TextBlock Visibility="{Binding MyText, Converter={StaticResource nullToVisibilityConverter}}"
FontSize="20"
FontStyle="Italic"
Foreground="Gray"
Text="None" />
Needed Solution:
<TextBlock FontSize="20"
Foreground="Black"
Text="{Binding MyText, TargetNullValue='None'}" />
<!-- plus any styles, templates or triggers, to change style of TextBlock for TargetNullValue -->
How can i use an alternativ style for a TargetNullValue. Any solutions using Styles, Triggers or Templates are welcome.
Note: this is for WPF, you might have to convert anything that doesn't quite mesh with SL5.0.
The easiest solution (unless this requirement is ubiquitous) is to make a specific style for each instance which binds to the same property as the textblock, checks for Null and sets properties there.
This example will paste nicely into Kaxaml.
<Style x:Key="tacoStyle" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource taco}}" Value="{x:Null}">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock Style="{StaticResource tacoStyle}" Text="{Binding Source={StaticResource taco}, TargetNullValue='bacon'}"/>
Of course if you need a more generic solution, you could extend TextBlock and add some logic to handle this.
<StackPanel>
<StackPanel.Resources>
<x:NullExtension x:Key="taco"/>
<Style x:Key="AltTacoStyle" TargetType="yourNS:ExtendedTextBlock">
<Setter Property="Foreground" Value="Pink"/>
</Style>
</StackPanel.Resources>
<yourNS:ExtendedTextBlock Text="{Binding Source={StaticResource taco}, TargetNullValue='bacon'}"
AltStyle="{StaticResource AltTacoStyle}"
AllowAltStyleOnNull="True"/>
</StackPanel>
And the control:
public class ExtendedTextBlock : TextBlock {
public static readonly DependencyProperty AllowAltStyleOnNullProperty =
DependencyProperty.Register("AllowAltStyleOnNull", typeof (bool), typeof (ExtendedTextBlock), new PropertyMetadata(default(bool)));
public bool AllowAltStyleOnNull {
get { return (bool) GetValue(AllowAltStyleOnNullProperty); }
set { SetValue(AllowAltStyleOnNullProperty, value); }
}
public static readonly DependencyProperty AltStyleProperty =
DependencyProperty.Register("AltStyle", typeof (Style), typeof (ExtendedTextBlock), new PropertyMetadata(default(Style)));
public Style AltStyle {
get { return (Style) GetValue(AltStyleProperty); }
set { SetValue(AltStyleProperty, value); }
}
static ExtendedTextBlock() {
TextProperty.OverrideMetadata(typeof(ExtendedTextBlock), new FrameworkPropertyMetadata(default(string), PropertyChangedCallback));
}
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) {
var tb = (ExtendedTextBlock)dependencyObject;
var binding = tb.GetBindingExpression(TextProperty);
if (binding != null && binding.DataItem == null) {
if (tb.AllowAltStyleOnNull)
tb.Style = tb.AltStyle;
}
}
}
You'll need to flesh that out a bit to be production-ready, but you get the idea.

Selecting a ListBoxItem when its inner ComboBox is focused

I have a DataTemplate that will be a templated ListBoxItem, this DataTemplate has a
ComboBox in it which when it has focus I want the ListBoxItem that this template
represents to become selected, this looks right to me. but sadly enough it doesn't work =(
So the real question here is within a DataTemplate is it possible to get or set the value
of the ListBoxItem.IsSelected property via a DataTemplate.Trigger?
<DataTemplate x:Key="myDataTemplate"
DataType="{x:Type local:myTemplateItem}">
<Grid x:Name="_LayoutRoot">
<ComboBox x:Name="testComboBox" />
</Grid>
<DataTemplate.Triggers>
<Trigger Property="IsFocused" value="true" SourceName="testComboBox">
<Setter Property="ListBoxItem.IsSelected" Value="true" />
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
<ListBox ItemTemplate="{StaticResource myDataTemplate}" />
I found a solution for your problem.
The problem is that when you have a control on your listboxitem, and the control is clicked (like for inputting text or changing the value of a combobox), the ListBoxItem does not get selected.
this should do the job:
public class FocusableListBox : ListBox
{
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is FocusableListBoxItem);
}
protected override System.Windows.DependencyObject GetContainerForItemOverride()
{
return new FocusableListBoxItem();
}
}
--> Use this FocusableListBox in stead of the default ListBox of WPF.
And use this ListBoxItem:
public class FocusableListBoxItem : ListBoxItem
{
public FocusableListBoxItem()
{
GotFocus += new RoutedEventHandler(FocusableListBoxItem_GotFocus);
}
void FocusableListBoxItem_GotFocus(object sender, RoutedEventArgs e)
{
object obj = ParentListBox.ItemContainerGenerator.ItemFromContainer(this);
ParentListBox.SelectedItem = obj;
}
private ListBox ParentListBox
{
get
{
return (ItemsControl.ItemsControlFromItemContainer(this) as ListBox);
}
}
}
A Treeview does also have this problem, but this solution does not work for a Treeview, 'cause SelectedItem of Treeview is readonly.
So if you can help me out with the Treeview please ;-)
I found that I preferred to use this:
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"></Setter>
</Trigger>
</Style.Triggers>
</Style>
Simple and works for all the listboxitems, regardless of what's inside.
No idea why your trigger don't work. To catch the get focus event of the combo box (or any control inside a listbox item) you can use attached routed events. You could put the code also in a derived listbox if you need this behavior in other parts of your application.
XAML:
<Window x:Class="RoutedEventDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Specialized="clr-namespace:System.Collections.Specialized;assembly=System"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="myDataTemplate">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" Margin="5,0"/>
<ComboBox Width="50">
<ComboBoxItem>AAA</ComboBoxItem>
<ComboBoxItem>BBB</ComboBoxItem>
</ComboBox>
</StackPanel>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemTemplate="{StaticResource myDataTemplate}">
<ListBox.ItemsSource>
<Specialized:StringCollection>
<System:String>Item 1</System:String>
<System:String>Item 2</System:String>
<System:String>Item 3</System:String>
</Specialized:StringCollection>
</ListBox.ItemsSource>
</ListBox>
</Grid>
</Window>
Code behind hooking up to all got focus events.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace RoutedEventDemo
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
EventManager.RegisterClassHandler(typeof(UIElement),
GotFocusEvent,
new RoutedEventHandler(OnGotFocus));
}
private static void OnGotFocus(object sender, RoutedEventArgs e)
{
// Check if element that got focus is contained by a listboxitem and
// in that case selected the listboxitem.
DependencyObject parent = e.OriginalSource as DependencyObject;
while (parent != null)
{
ListBoxItem clickedOnItem = parent as ListBoxItem;
if (clickedOnItem != null)
{
clickedOnItem.IsSelected = true;
return;
}
parent = VisualTreeHelper.GetParent(parent);
}
}
}
}

Resources