Why would using Caliburn Action inside a Blend KeyTrigger break my View? - wpf

Caliburn Micro 2.0.2 and WPF 4 and .NET 4.5.
Using a very simple view and a very simple viewmodel to demonstrate, I notice that if I have a Caliburn Action inside a KeyTrigger, my View is not activated properly. When I say 'properly' I mean that if I look at it in Snoop, the whole UserControl is disabled. If I change it to a Window and open it as Window rather than an embedded control, Snoop cannot even find that Window and the control remains disabled.
Here is my ViewModel:
using System.Windows.Input;
using Caliburn.Micro;
namespace PlainSailTech.ViewModels.Admin
{
public class SystemConfigurationViewModel : Screen
{
private bool canShowDevLinks;
public bool CanShowDevLinks
{
get { return canShowDevLinks; }
set
{
if (value.Equals(canShowDevLinks)) return;
canShowDevLinks = value;
}
}
public void ShowDevLinks(KeyEventArgs args)
{
if (args != null)
args.Handled = true;
CanShowDevLinks = !CanShowDevLinks;
}
}
}
Here is my XAML:
<Window x:Class="PlainSailTech.Bookkeeping.Ui.Views.Admin.SystemConfigurationView"
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:cal="http://www.caliburnproject.org"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
mc:Ignorable="d"
Title="Caliburn Action test"
d:DesignHeight="300" d:DesignWidth="300">
<i:Interaction.Triggers>
<ei:KeyTrigger Modifiers="Ctrl" Key="D" FiredOn="KeyUp" ActiveOnFocus="True">
<cal:ActionMessage MethodName="ShowDevLinks">
<cal:Parameter Value="$eventArgs" />
</cal:ActionMessage>
</ei:KeyTrigger>
</i:Interaction.Triggers>
<Grid>
<Grid Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Background="#FFFBFBFB" Visibility="{Binding CanShowDevLinks, Converter={StaticResource BoolToVisible}}">
<TextBlock Text="Dev links here" />
</Grid>
<Grid Grid.Row="1">
<TextBox VerticalAlignment="Top" IsReadOnly="False" IsEnabled="True" Width="150" Height="30">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Background" Value="Blue"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Text" Value="Enabled"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Text" Value="Disabled"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Grid>
</Grid>
</Grid>
</Window>
Notice that the TextBox is set to enabled but the whole control is being disabled. You can see this from this screen shot:
However, if I simply remove the Caliburn Action, everything is good again:
<i:Interaction.Triggers>
<ei:KeyTrigger Modifiers="Ctrl" Key="D" FiredOn="KeyUp" ActiveOnFocus="True">
</ei:KeyTrigger>
</i:Interaction.Triggers>
A few possible clues. The problem goes away if:
If I replace the Caliburn Action with:
<ei:ChangePropertyAction TargetObject="{Binding Mode=OneWay}" PropertyName="CanShowDevLinks" Value="True">
Or my ViewModel does not inherit from Screen or PropertyChangedBase.
If I override OnViewAttached and wire up a handler for EnableChanged, I can see the IsEnabled being set to false after the View is attached.
So it seems like an interaction between whatever is happening in the Screen/PropertyChangedBase base class and the wiring up of the Action.

Okay - the answer to this question is the person who asked it is an idiot. I have a guard property that has the name of the action's method. Because the KeyTrigger is below the UserControl, it is disabling the whole thing. Arghhh...
I knew that a guard property applied when you named a button, for example, but I didn't realise that it applied to any UI element that has an action attached to it. You live and learn, occasionally.

Related

WPF XAML Local (child) styles are being overwritten. Why?

I wrote a simple dialog (XAML/WPF) and a test app and the dialog looks fine. In particular the buttons in the ListView have rounded corners. I've posted a picture and the code below.
The problem? When I use this dialog inside a much larger program (codebase too large to share), the rounded corners and other styling is gone. I strongly suspect something in the larger program is taking precedence over my local work. Perhaps a global style for buttons or some such thing?
I'd like to understand what is going on. Presumably something in the main app takes precedence over my xaml work?
I'd like to know if there is a way to say "don't inherit styles from the app itself. Rather use WPF defaults unless I override them.", assuming that is the problem.
See picture (notice rounded corners)
See picture from when I call it from actual main application instead of test application
Notice in particular lack of rounded corners. My work to produce rounded corners is gone! Also, in the test app, hovering over a button shows a blue color which I assume is default (I didn't do it). When called from main app, no such hover effect. I suspect the main app gets rid of that somewhere.
Here's the simple dialog xaml
<Window x:Class="FirmsDialog"
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:Dialogs"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="400"
Width="390" Height="720" BorderBrush="LightGray"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
x:Name="FirmsViewDlg" Loaded="FirmsViewDlg_Loaded"
>
<Window.Resources>
<FontFamily x:Key="AvenirNextforCompany">
pack://application:,,,/Assets/Fonts/#AvenirNextforCompany
</FontFamily>
<local:ReverseObjectToBool x:Key="ReverseObjectToBoolConverter" />
<local:ObjectToBool x:Key="ObjectToBoolConverter" />
<!-- New style -->
<Style x:Key="StyleListViewItem" TargetType="ListViewItem">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Green"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Blue"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- Existing style -->
<Style x:Key="StyleListView" TargetType="ListView">
<Setter Property="ItemContainerStyle" Value="{StaticResource StyleListViewItem}"/>
<Setter Property="SnapsToDevicePixels" Value="true"/>
<!-- ... -->
</Style>
</Window.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Content="Select Company" HorizontalAlignment="Center" FontSize="20px" Foreground="#393a3d" FontFamily="{StaticResource AvenirNextforCompany}" FontWeight="Normal" ></Label>
<Label Grid.Row="1" Content="Accountant companies" Margin="10,0" FontFamily="{StaticResource AvenirNextforCompany}" FontSize="14px" Foreground="#8d9096" FontWeight="Normal"></Label>
<ListView BorderThickness="0" Grid.Row="2" ItemsSource="{Binding RealmMembershipInfo}" SelectedItem="{Binding SelectedFirm}" x:Name="realmListBox"
HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch" Height="Auto" Margin="0,0,0,0"
VerticalAlignment="Top" FontWeight="Bold" FontSize="14" Background="White"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
BorderBrush="LightGray">
<ListView.ItemTemplate>
<DataTemplate>
<Button Click="Button_Click"
MinHeight="65" Padding="10,0,10,0"
Margin="0,0,0,0" HorizontalContentAlignment="Left" HorizontalAlignment="Stretch" BorderBrush="LightGray" Background="White" Foreground="#393a3d" FontFamily="{StaticResource AvenirNextforCompany}" FontSize="14px" FontWeight="SemiBold"
>
<Button.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="9"/>
</Style>
</Button.Resources>
<TextBlock Text="{Binding displayName}" TextWrapping="Wrap" HorizontalAlignment="Stretch" Foreground="#393a3d" FontFamily="{StaticResource AvenirNextforCompany}" FontSize="14px" FontWeight="Bold" >
</TextBlock>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Margin" Value="10,10,10,10"/>
<Setter Property="Padding" Value="0"/>
<Style.Triggers>
<Trigger Property="Control.IsMouseOver" Value="True">
<Setter Property="Control.Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
</Window>
Simple program to show the Xaml above
using Dialogs;
using Models;
using System.Collections.Generic;
using System.Windows;
namespace TestApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_SelectFirm(object sender, RoutedEventArgs e)
{
// hack code to set up dialog
RealmMembershipInfo realmInfo = new RealmMembershipInfo();
realmInfo.realmMembershipInfo = new List<RealmMembershipItem>();
RealmMembershipItem item = new RealmMembershipItem();
item.displayName = "Company 1";
realmInfo.realmMembershipInfo.Add(item);
item = new RealmMembershipItem();
item.displayName = "Company2";
realmInfo.realmMembershipInfo.Add(item);
FirmsDialog dlg = new FirmsDialog(realmInfo);
dlg.ShowDialog();
MessageBox.Show("Your picked firm: " + dlg.SelectedFirm);
}
private void Button_SelectClient(object sender, RoutedEventArgs e)
{
}
}
}
You haven't set the style on the Listview.
<ListView Style={StaticResource StyleListView} ....
And you are using the default style for the ItemsContainer. You need to add the a base style.
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem" BasedOn={StaticResource StyleListViewItem}...

WPF DataTriggers on ContentControls are firing after their bound Dependency Property's PropertyChangedCallback

I'm coding a WPF input dialog window, that will show a different Control based on a dependency property named InputType.
The language I'm using is Visual COBOL .NET, but the issue is not related to the language but to WPF itself, and the language is easily understandable by VB and C# programmers.
This is the XAML code for my dialog window
<Window x:Name="wndDialog"
x:Class="ClassLibraryNew.AGInputBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lib="clr-namespace:ClassLibraryNew"
xmlns:ctrl="clr-namespace:ClassLibraryNew.Controls"
Width="400"
MinHeight="200"
WindowStyle="None"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize"
Background="#FFEEEEEE"
SizeToContent="Height"
MouseDown="OnMouseDown"
Loaded="OnLoaded">
<Window.CommandBindings>
<CommandBinding Command="{x:Static lib:DialogCommands.OkCommand}" CanExecute="OnCommandCanExecute" Executed="OnCommandExecuted"/>
<CommandBinding Command="{x:Static lib:DialogCommands.CancelCommand}" CanExecute="OnCommandCanExecute" Executed="OnCommandExecuted"/>
</Window.CommandBindings>
<Border Style="{StaticResource AGTWindowBorder}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="{Binding Caption}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="{Binding Text}"
FontSize="{Binding Converter={StaticResource FontSizeConverter}, ConverterParameter='16'}"
VerticalAlignment="Center"
TextWrapping="Wrap"/>
<ContentControl Grid.Row="1" x:Name="contentControl" >
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Foreground="Red" Text="Errore: input type non valido. Contattare l'assistenza tecnica."/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding InputType}" Value="Text">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ctrl:TextField Text="{Binding Value, ElementName=wndDialog}"
ctrl:WatermarkService.Watermark="{Binding WatermarkText, ElementName=wndDialog}"
ctrl:WatermarkService.HideWhenFocused="False"
MaxLength="{Binding MaxLength, ElementName=wndDialog}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="Integer">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ctrl:IntegerField Value="{Binding Value, ElementName=wndDialog}"
ZeroFill="{Binding ZeroFill, ElementName=wndDialog}"
MaxLength="{Binding MaxLength, ElementName=wndDialog}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="Decimal">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ctrl:DecimalField Value="{Binding Value, ElementName=wndDialog}"
DecimalDigits="{Binding DecimalDigits, ElementName=wndDialog}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</GroupBox>
<StackPanel Grid.Row="1"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="8">
<StackPanel.Resources>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource EuroButton}">
<Setter Property="Width" Value="80"/>
<Setter Property="Margin" Value="8,0,0,0"/>
</Style>
</StackPanel.Resources>
<Button IsDefault="True"
Command="{x:Static lib:DialogCommands.OkCommand}">
<AccessText Text="_Ok"/>
</Button>
<Button IsCancel="True"
Command="{x:Static lib:DialogCommands.CancelCommand}">
<AccessText Text="_Annulla"/>
</Button>
</StackPanel>
</Grid>
</Border>
What I want to achieve is showing the user a 'TextField' (custom TextBox) when the InputType is Text, 'IntegerField' when the InputType is Integer, etc..
InputType's type is an enum name DialogInputType which contains three values (Text, Integer, Decimal).
This works fine, however I need a way to to attach an event handler to the Field inside the ContentControl when its content has been correctly set and is not null.
I expected the DataTriggers to re-evaluate when the InputType changed, instead this fails:
(Visual COBOL .NET)
01 InputTypeProperty type DependencyProperty public static initialize only
value type DependencyProperty::Register(
"InputType",
type of DialogInputType,
type of AGInputBox,
new FrameworkPropertyMetadata(
type DialogInputType::Text,
new PropertyChangedCallback(method OnInputTypeChanged)
)
).
*> property definition omitted...
method-id OnInputTypeChanged private static.
procedure division using by value sender as type DependencyObject, by value e as type DependencyPropertyChangedEventArgs.
if sender not instance of type AGInputBox
goback
end-if
declare wnd as type AGInputBox = sender as type AGInputBox
if wnd::contentControl::Content instance of type FieldBase *> debugger arrives here
declare textField as type FieldBase
set textField = wnd::contentControl::Content as type FieldBase
attach method wnd::OnFieldTemplateApplied to textField::TemplateApplied
end-if
end method.
The VS debugger shows that the ContentControl's Content is null, but then the window is correctly visualized, and maybe its content is set later...
It is also null in:
- Loaded Event of the Window
- ContentRendered Event of the Window
And I can't set a Loaded RoutedEventHandler inside the DataTemplate Control, neither with Loaded="OnFieldLoaded" nor with Style + EventSetter, because it's forbidden and won't compile (even if the compiler error suggests to use the EventSetter :/).
Edit: I tried l33t's solution but unfortunately OnContentChanges is never getting called, even if the content is correctly set.
I created this class:
class-id ClassLibraryNew.Controls.NotifyingContentControl public
inherits type ContentControl.
01 ContentChanged type EventHandler event public.
method-id new public.
procedure division.
invoke super::new()
end method.
method-id OnContentChanged protected override.
procedure division using by value oldContent as object, by value newContent as object.
invoke super::OnContentChanged(oldContent, newContent) *> I put a debugger breakpoint here but it's not getting hit
invoke RaiseContentChanged()
end method.
method-id RaiseContentChanged private.
procedure division.
declare handler as type EventHandler = ContentChanged
declare e as type EventArgs = new EventArgs()
if handler not = null
invoke run handler(by value self, e)
end-if
end method.
end class.
Define the DataTemplates as resources and handle the Loaded event of the TextField, IntegerField and DecimalField root elements:
<ContentControl Grid.Row="1" x:Name="contentControl" >
<ContentControl.Resources>
<DataTemplate x:Key="tfTemplate">
<ctrl:TextField ... Loaded="LoadedHandler"/>
</DataTemplate>
<!-- + DataTemplates for IntegerField and DecimalField -->
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Foreground="Red" Text="Errore: input type non valido. Contattare l'assistenza tecnica."/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding InputType}" Value="Text">
<Setter Property="ContentTemplate" Value="{StaticResource tfTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="Integer">
<Setter Property="ContentTemplate" Value="{StaticResource ifTemplate}" />
</DataTrigger>
...
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Even if you manage to determine the value of the content, there is no guarantee that this is the value you see in the UI.
You can try this:
public class ContentControlEx : ContentControl
{
protected override void OnContentChanged(object oldContent, object newContent)
{
// Do stuff...
base.OnContentChanged(oldContent, newContent);
}
}
Then use ContentControlEx in place of the regular one.

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>

Make DatePicker easier to read when disabled

The text is grayed out when the DatePicker is disabled and I want the content to be easier to read.
What I did on some TextBoxes was:
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="Black" />
</Trigger>
</Style.Triggers>
</Style>
It did make the text easier to read.
I do manage to change the Foreground colour on the DataPicker but it does not do the trick. The text was still grayed out.
Seems like there is another property I need to set to make the content of the disabled DatePicker easier to read.
So, how do I make the content of my disabled DatePicker easier to read?
Can you extend DatePicker by adding bool DependencyProperty called Editable.
I found a working example at the following link, note that I run this code in .NET 4.
Here is the DatePicker Control:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
namespace DatePickerStyle
{
public class ExtendedDatePicker : DatePicker
{
public static readonly DependencyProperty EditableProperty = DependencyProperty.Register("Editable", typeof(bool),
typeof(ExtendedDatePicker), new PropertyMetadata(true));
public bool Editable
{
get { return (bool)GetValue(EditableProperty); }
set { SetValue(EditableProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var textBox = GetTemplateChild("PART_TextBox") as DatePickerTextBox;
var binding = new Binding { Source = this, Path = new PropertyPath(ExtendedDatePicker.EditableProperty) };
textBox.SetBinding(UIElement.FocusableProperty, binding);
}
}
}
Here is the XAML:
<Window x:Class="DatePickerStyle.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:DatePickerStyle="clr-namespace:DatePickerStyle"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DatePicker IsEnabled="True" Grid.Row="0" SelectedDate="2002/12/31"/>
<DatePicker IsEnabled="False" Grid.Row="1" SelectedDate="2002/12/31"/>
<DatePickerStyle:ExtendedDatePicker Editable="True" Grid.Row="2" SelectedDate="2002/12/31"/>
<DatePickerStyle:ExtendedDatePicker Editable="False" Grid.Row="3" SelectedDate="2002/12/31"/>
</Grid>
</Window>
My I suggest this simpler, universal approach?
<ControlTemplate x:Key="MyDisabledDatePicker">
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock
Text="{Binding Path=SelectedDate, StringFormat={}{0:d}, RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center" HorizontalAlignment="Left" Padding="10,0,0,0"/>
</Border>
</ControlTemplate>
<Style TargetType="{x:Type DatePicker}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}}" Value="false">
<Setter Property="Template" Value="{StaticResource MyDisabledDatePicker}" />
</DataTrigger>
</Style.Triggers>
</Style>
Presto!
the above code w ExtendedDatePicker works, first I thought it didn't but that was because the dropdown could still change the text and the Editable="False" doesn't work on the dropdown
so don't forget to add the following to the ExtendedDatePicker
Editable="False" AllowDrop="False" IsDropDownOpen="False" IsHitTestVisible="False" IsManipulationEnabled="False"

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