Change ListBox.ItemsSource Binding property on Button.Click? - wpf

Quick question...
I have a ListBox with its ItemsSource property bound to a collection property in a viewmodel like so:
<ListBox Name="CollectionsListBox" ItemsSource="{Binding Activity.Timesheets}" />
I also have two Button objects in the same view. The question is... can I change the CollectionsListBox ItemsSource Binding from Activity.Timesheets to Activity.Attachments using just XAML?
Failing that, from the viewmodel using Command objects?
EDIT >>>
I found a simple solution by using RadioButtons instead of Buttons from part of Howard's answer:
<ListBox Name="CollectionsListBox">
<ListBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TimesheetsButton,Path=IsChecked}" Value="True">
<Setter Property="ListBox.ItemsSource" Value="{Binding Activity.Timesheets}" />
<Setter Property="ListBox.ItemContainerStyle" Value="{StaticResource TimesheetStyle}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=AttachmentsButton,Path=IsChecked}" Value="True">
<Setter Property="ListBox.ItemsSource" Value="{Binding Activity.Attachments}" />
<Setter Property="ListBox.ItemContainerStyle" Value="{StaticResource AttachmentStyle}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
Many thanks for the help.

I'm not sure if Button can do this. But radiobutton can satisfy you only in XAML.
Let's say we have two enum:
public enum E { A = 0, B = 1, C = 2 }
public enum F { G = 0, H = 1, L = 2 }
I defined them as resource in the XAML:
<ObjectDataProvider ObjectType="{x:Type sys:Enum}" MethodName="GetValues" x:Key="EProvider">
<ObjectDataProvider.MethodParameters>
<x:TypeExtension Type="{x:Type local:E}" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider ObjectType="{x:Type sys:Enum}" MethodName="GetValues" x:Key="FProvider">
<ObjectDataProvider.MethodParameters>
<x:TypeExtension Type="{x:Type local:F}" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
Then here we go:
<ListBox x:Name="List1">
<ListBox.Style>
<Style>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=Rdb1,Path=IsChecked}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="ListBox.ItemsSource" Value="{Binding Source={StaticResource EProvider}}" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=Rdb2,Path=IsChecked}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="ListBox.ItemsSource" Value="{Binding Source={StaticResource FProvider}}" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
<RadioButton x:Name="Rdb1" GroupName="Group1" />
<RadioButton x:Name="Rdb2" GroupName="Group1" />

As much I understand this is what you need -
Here is how i did
1)Code Behind (with two enums)
public enum Enum1{R = 0, O = 1,H = 2,I = 3,T = 4}
public enum Enum2{A = 0,S = 1, I = 2,T = 3}
public partial class Window1 : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool toggleItemSource;
public bool ToggleItemSource
{
get
{
return this.toggleItemSource;
}
set
{
this.toggleItemSource = value;
this.PropertyChanged(this, new PropertyChangedEventArgs("ToggleItemSource"));
}
}
public Window1()
{
InitializeComponent();
this.DataContext = this;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
this.ToggleItemSource = this.ToggleItemSource ? false : true;
}
}
XAML
<Window x:Class="Listbox_with_dynamic_data_source.Window1"
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"
xmlns:local="clr-namespace:Listbox_with_dynamic_data_source"
Title="Window1" Height="300" Width="300">
<ObjectDataProvider ObjectType="{x:Type sys:Enum}"
MethodName="GetValues"
x:Key="Enum2Provider">
<ObjectDataProvider.MethodParameters>
<x:TypeExtension Type="{x:Type local:Enum2}" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<Grid>
<!-- ListBox-->
<ListBox x:Name="DynamicListBox"
Padding="10" HorizontalAlignment="Left" Width="52" Margin="20,21,0,115">
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="ItemsSource"
Value="{Binding Source={StaticResource Enum1Provider}}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ToggleItemSource,
UpdateSourceTrigger=PropertyChanged
}"
Value="False">
<Setter Property="ItemsSource"
Value="{Binding Source={StaticResource Enum2Provider}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
<!-- Toggle Button -->
<Button Height="29"
Margin="94,45.44,59,0"
Name="button1"
VerticalAlignment="Top"
Click="button1_Click"
Content="ToggleItemSource" />
</Grid>
Clicking on Toggle Item Source Button will toggle the Items Source

To my surprise the following seems to work:
<ListBox Name="myLB" ItemsSource="{Binding Data}"/>
<Button Content="This is a Button">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="myLB"
Storyboard.TargetProperty="ItemsSource">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{Binding Data2}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
Edit: If this works apparently depends on the nature of the itemssource. Animations are a bit messy in that regard. Using constant states is better e.g. as suggested with Radiobuttons since then Setters can be used.

Related

ContextMenu is empty on first left-click of wpf button

I have a button in my view with a dynamic context menu that I want to activate via right or left clicks. When I left-click first, the menu is empty. When I right-click, or left-click after a right-click, the menu shows with the contents of favouriteItemList.
I have looked at this, which looks like it might be an answer, but I don't understand enough about WPF to get it to work with my code.
The other thing is that SelectFavouriteCommand doesn't seem to be called when I click an item in the menu.
View model class:
{
public ViewModel(IFolderService folderService)
{
this.SelectFavouriteCommand = new MvxAsyncCommand(this.SelectFavourite);
this.favouriteItemList = new MvxObservableCollection<SelectedFolder>(folderService.GetFolders());
}
public IMvxAsyncCommand SelectFavouriteCommand { get; }
private MvxObservableCollection<SelectedFolder> favouriteItemList;
public MvxObservableCollection<SelectedFolder> FavouriteItemList
{
get => this.favouriteItemList;
set
{
this.favouriteItemList = value;
RaisePropertyChanged(() => this.FavouriteItemList);
}
}
private async Task SelectFavourite()
{
throw new NotImplementedException();
}
}
In the view xml:
<Button DockPanel.Dock="Right" x:Name="BtnUseFavourite" Height="25" Width="25" Margin="0,0,5,0">
<StackPanel>
<Image Source="/Images/ImageFavoriteIcon.png" />
</StackPanel>
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<EventTrigger RoutedEvent="Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding FavouriteItemList}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Path}"></Setter>
<Setter Property="Command" Value="{Binding SelectFavouriteCommand}" ></Setter>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Button.ContextMenu>
</Button>
For calling command try below code
<Setter Property="Command"
Value="{Binding Path=DataContext.SelectFavouriteCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
For you to be able to invoke the SelectFavouriteCommand when clicking on the MenuItem, you should bind to the Button's DataContext using the PlacementTarget property of the ContextMenu:
<ContextMenu ItemsSource="{Binding FavouriteItemList}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Path}"></Setter>
<Setter Property="Command" Value="{Binding PlacementTarget.DataContext.SelectFavouriteCommand,
RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
And a context menu is only expected to be opened when right-clicking on a Button. Left-clicking would invoke a command or a click event handler.

EditorTemplate for TemplateColumn in XamGrid not working

I have a XamGrid with two columns, Name and Type. Depending on Type, I want to have a different kind of column for Name, thus I'm using a TemplateColumn. In the data template I have a ContentControl with a default ContentTemplate and a DataTrigger that sets the ContentTemplate to a different column style if Type is a specific value.
I am setting alll four templates (ItemTemplate, EditorTemplate, AddNewRowItemTemplate, AddNewRowEditorTemplate) of the TemplateColumn to this data template.
ItemTemplate, AddNewRowItemTemplate and AddNewRowEditorTemplate work as intended, however EditorTemplate does not, see attached pictures:
Here is my code:
MainWindow.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:ig="http://schemas.infragistics.com/xaml"
Width="640" Height="480" >
<Window.Resources>
<DataTemplate x:Key="EditorTemplate">
<TextBox Width="64"/>
</DataTemplate>
<DataTemplate x:Key="BoolEditorTemplate">
<CheckBox/>
</DataTemplate>
<DataTemplate x:Key="DataTemplate">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource EditorTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="bool">
<Setter Property="ContentTemplate" Value="{StaticResource BoolEditorTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</Window.Resources>
<ig:XamGrid ItemsSource="{Binding DataCollection, RelativeSource={RelativeSource AncestorType=Window}}"
AutoGenerateColumns="False">
<ig:XamGrid.EditingSettings>
<ig:EditingSettings AllowEditing="Row" />
</ig:XamGrid.EditingSettings>
<ig:XamGrid.AddNewRowSettings>
<ig:AddNewRowSettings AllowAddNewRow="Top" />
</ig:XamGrid.AddNewRowSettings>
<ig:XamGrid.Columns>
<ig:TemplateColumn Key="Name"
ItemTemplate="{StaticResource DataTemplate}"
AddNewRowItemTemplate="{StaticResource DataTemplate}"
EditorTemplate="{StaticResource DataTemplate}"
AddNewRowEditorTemplate="{StaticResource DataTemplate}"/>
<ig:TextColumn Key="Type"/>
</ig:XamGrid.Columns>
</ig:XamGrid>
</Window>
MainWindow.xaml.cs:
using System.Collections.ObjectModel;
namespace WpfApplication1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
public ObservableCollection<Data> DataCollection { get; } = new ObservableCollection<Data>
{
new Data { Name = "Foo", Type = "bool" },
new Data { Name = "Bar", Type = "enum" }
};
}
public class Data
{
public string Name { get; set; }
public string Type { get; set; }
}
}
As explained here on the infragistics forum, for this use case not only is an EditorTemplate needed, but also an EditorStyle.
<Style x:Key="EditorStyle" TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource EditorTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="bool">
<Setter Property="ContentTemplate" Value="{StaticResource BoolEditorTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="DataTemplate">
<ContentControl Content="{Binding }"
Style="{StaticResource EditorStyle}" />>
</DataTemplate>
[...]
<ig:TemplateColumn Key="Name"
ItemTemplate="{StaticResource DataTemplate}"
AddNewRowItemTemplate="{StaticResource DataTemplate}"
EditorTemplate="{StaticResource DataTemplate}"
AddNewRowEditorTemplate="{StaticResource DataTemplate}"
EditorStyle="{StaticResource EditorStyle}" />

Switch images with a data template

I know that already some questions exist but I can not fix my problem with them.
Problem: I try to change a image with a data template but just the default image is visible.
Code:
My xaml code is like this:
<Window.Resources>
<DataTemplate x:Key="MultiTemplate">
<Image Height="17" Width="17">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="{svg2Xaml:SvgImage VideoControllerTester;component/Resources/Start.svg}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MultiTrigger}" Value="start">
<Setter Property="Source" Value="{svg2Xaml:SvgImage VideoControllerTester;component/Resources/Start.svg}"/>
</DataTrigger>
<DataTrigger Binding="{Binding MultiTrigger}" Value="stop">
<Setter Property="Source" Value="{svg2Xaml:SvgImage VideoControllerTester;component/Resources/Stop.svg}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</DataTemplate>
</Window.Resources>
<ContentControl ContentTemplate="{DynamicResource MultiTemplate}"/>
In the code behind I set MultiTrigger = "start" or "stop".
Question: Can I show the images with the content control? Or I do some dumb stuff with the data template?
Edit:
public string MultiTrigger
{
get { return _multiTrigger; }
set
{
_multiTrigger = value;
RaisePropertyChanged();
}
}
Assuming that there is a MainViewModel class with a MultiTrigger property (which btw. is a strange property name), you would assign an instance of the view model class to the MainWindow's DataContext, either in code behind:
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
Or in XAML:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
Then you would declare the Image Style as a resource:
<Window.Resources>
<Style TargetType="Image" x:Key="ImageStyle">
<Setter Property="Source" Value="{svg2Xaml:SvgImage VideoControllerTester;component/Resources/Start.svg}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MultiTrigger}" Value="stop">
<Setter Property="Source" Value="{svg2Xaml:SvgImage VideoControllerTester;component/Resources/Stop.svg}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
and apply it to an Image control:
<Image Style="{StaticResource ImageStyle}"/>
Then change the property value somewhere in the MainWindow's code behind by directly accessing the view model instance like this:
((MainViewModel)DataContext).MultiTrigger = "stop";

wpf - validation - how to show tooltips and disable "run" button

Hi
I need to validate some of textboxes in my application. I decied to use validation rule
"DataErrorValidationRule". That's why in my class I implemented IDataErrorInfo interface and wrote aproperiate functions. In my xaml code I added bindings and validation rules to textboxes
<TextBox x:Name="txtName" Grid.Column="3" Grid.Row="1" TextAlignment="Center" >
<TextBox.Text>
<Binding Path="Name" >
<Binding.ValidationRules>
<DataErrorValidationRule></DataErrorValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Validation of this textbox is OK - I mean red frame appears on textbox if data is wrong. However what I need to do is to show tooltip on that textbox, but what is more important I have to disable button "Run" if any textboxes have wrong data. What is the best way to do taht ??
EDIT
First problem was solved, but I have an another. I need to use MultiBindings to validate my Button. So I did sth like that
<Button x:Name="btnArrange" Grid.Column="0" Content="Rozmieść" Click="btnArrange_Click" >
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Value="False">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource BindingConverter}">
<Binding ElementName="txtName" Path="Validation.HasError" />
<Binding ElementName="txtSurname" Path="Validation.HasError"/>
<Binding ElementName="txtAddress" Path="Validation.HasError"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
My Converter looks like that
public class Converters : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(values !=null && values.Length > 0)
{
if (values.Cast<type>().Count(val => val) > 0)
return false;
return true;
}
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
#endregion
}
However I get invalidCastException in this converter. What is a proper cast in that case? I thoght as if HasError is a bool type so I should cast to bool.
To show the error message in a tool tip put this into your Application.Resources:
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
( Example from http://msdn.microsoft.com/en-us/library/system.windows.controls.validation.errortemplate.aspx )
To enable/disable a button you could use something along the line of
<Button x:Name="btnOK" Content="OK" IsDefault="True" Click="btnOK_Click">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="false" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=txt1, Path=(Validation.HasError)}" Value="false" />
<Condition Binding="{Binding ElementName=txt2, Path=(Validation.HasError)}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
or you could implement ICommand and use command binding.
EDIT
Here is a fully working example. It displays a window with two TextBoxes. The Button is enabled if and only if both TextBoxes are non-empty. Create a project called ValidationDemo and put the following files in it:
MainWindow.xaml:
<Window x:Class="ValidationDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="146" Width="223">
<Window.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Label Content="A" Height="28" HorizontalAlignment="Left" Margin="46,7,0,0" Name="label1" VerticalAlignment="Top" />
<TextBox Name="txtA" Text="{Binding Path=TextA, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Height="23" HorizontalAlignment="Left" Margin="69,12,0,0" VerticalAlignment="Top" Width="120" />
<Label Content="B" Height="28" HorizontalAlignment="Left" Margin="46,39,0,0" Name="label2" VerticalAlignment="Top" />
<TextBox Name="txtB" Text="{Binding Path=TextB, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Height="23" HorizontalAlignment="Left" Margin="69,41,0,0" VerticalAlignment="Top" Width="120" />
<Button Name="btnOk" Content="OK" Height="23" HorizontalAlignment="Left" Margin="114,70,0,0" VerticalAlignment="Top" Width="75" Click="btnOk_Click">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="false" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=txtA, Path=(Validation.HasError)}" Value="false" />
<Condition Binding="{Binding ElementName=txtB, Path=(Validation.HasError)}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace ValidationDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Model model = new Model();
public MainWindow()
{
InitializeComponent();
this.DataContext = this.model;
}
private void btnOk_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
}
}
Model.cs:
using System;
using System.ComponentModel;
namespace ValidationDemo
{
public class Model : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
private string textA = string.Empty;
public string TextA
{
get
{
return this.textA;
}
set
{
if (this.textA != value)
{
this.textA = value;
this.OnPropertyChanged("TextA");
}
}
}
private string textB = string.Empty;
public string TextB
{
get
{
return this.textB;
}
set
{
if (this.textB != value)
{
this.textB = value;
this.OnPropertyChanged("TextB");
}
}
}
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string result = string.Empty;
switch (columnName)
{
case "TextA":
if (string.IsNullOrEmpty(this.textA))
{
result = "'A' must not be empty";
}
break;
case "TextB":
if (string.IsNullOrEmpty(this.textA))
{
result = "'B' must not be empty";
}
break;
}
return result;
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
<Window.Resources>
<Style x:Key="ElementInError" TargetType="{x:Type FrameworkElement}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<!-- ... -->
<TextBox x:Name="txtName" Style="{StaticResource ElementInError}">
<!-- ... -->
</TextBox>
<!-- ... -->
<Button x:Name="OkButton" Content="Ok" Margin="5" Click="OkButton_Click">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=txtName,Path=(Validation.HasError)}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
If you create a validation summary you can bind the IsEnabled property of your "Run" button to it's HasErrors property.
You'll need to use an intermediate property or converter as you want the IsEnabled to be true when HasErrors is false (and vice versa).

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