I want to have ListBoxItem trigger two events which I can catch from the outer usercontrol that contains the ListBox. Here is what I got so far:
<ListBox
Background="Black"
Selected="listbox_selected"
x:Name="listBox">
<ListBox.Resources>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver,RelativeSource={RelativeSource Self}}"
Value="True">
<Setter Property="IsSelected" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
Now, this calls my listbox_Selected event. What I want is a calling a different event or property when IsMouseOver. Just to make it clear, I know how to change the background/foreground or other properties of the ListBoxItem itself. But I want to change something of the grandparent.
You already have that event... Handle a static routed event from ListBoxItem class called "Selected" (and there is also "UnSelected") at any ancestor, provided that we dont handle "Selection" event anywhere in the descendents tree ...
<Window x:Class="...."
...
ListBoxItem.Selected="OnListBoxSelected">
<Grid>
<ListBox ItemsSource="{Binding Employees}"
DispalyMemberPath="Name"
selectedValuePath="ID" >
<ListBox.Resources>
<Style TargetType="ListBoxItem"
BasedOn="{StaticResource
{x:Type ListBoxItem}}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver,
RelativeSource={RelativeSource
Self}}"
Value="True">
<Setter Property="IsSelected"
Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
</Grid>
</Window>
And in code behind ...
private void OnListBoxSelected(object sender, RoutedEventArgs e)
{
var window = sender as Window;
var listBoxItem = e.OriginalSource as ListBoxItem;
var selectedItem = listBoxItem.DataContext;
}
Hope this helps...
Related
How can I get the SelectedItem of a ListBox to be highlighted after setting the SelectedItem in the ViewModel?
The ItemsSource is bound to an ObservableCollection of Bar (the collection is a member of a class Foo. A button is bound to a command that adds a new empty Bar instance to the collection and then also sets SelectedItem to the new empty instance.
After adding the instance to the collection, the ListBox is updated to show the new blank Bar. However, after setting the SelectedItem property in the ViewModel, the new instance is not highlighted in the ListBox but it is being set and the PropertyChanged event is raised (the SelectedItem is displayed elsewhere in the View).
Additional Details:
INotifyPropertyChanged is implemented in a base ViewModel class, and also implemented in the Foo and Bar classes.
The ListBox contains a custom ItemTemplate to display Bar members, and a custom ItemContainerStyle that modifies the Background for the IsMouseOver trigger.
simplified xaml:
<ListBox ItemsSource="{Binding Path=MyFoo.BarCollection}"
SelectedItem="{Binding Path=SelectedItem,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<Button Content="Add New Bar"
Command="{Binding Path=AddBarCommand}"/>
simplified viewmodel:
private Foo _myFoo;
public Foo MyFoo
{
get { return _myFoo; }
set { _myFoo= value; OnPropertyChanged("MyFoo"); }
}
private Bar _selectedItem;
public Bar SelectedItem
{
get { return _selectedItem; }
set { _selectedItem = value; OnPropertyChanged("SelectedItem"); }
}
private void AddBar()
{
Bar newBar = new Bar();
MyFoo.BarCollection.Add(newBar);
SelectedItem = newBar ;
_unsavedChanges = true;
}
Your code worked perfectly for me.
Notice that "Bar 3" is selected, but when the listbox doesn't have focus, the default theme on Windows 7 works pretty hard to keep you from noticing. And Windows 7 isn't even the worst of the bunch. Our application uses WhiteSmoke as a default background, and we've had major issues with older users1 being unable to tell if listbox items are selected or not.
Fortunately, it's a trivial fix. These resources could just as easily be in Window.Resources, in a global ListBox style, or in App.xaml. It's up to you how widely you want to apply them.
<ListBox
ItemsSource="{Binding MyFoo.BarCollection}"
SelectedItem="{Binding SelectedItem}"
>
<ListBox.Resources>
<SolidColorBrush
x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}"
Color="{x:Static SystemColors.HighlightColor}"
Opacity="0.5"
/>
<SolidColorBrush
x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}"
Color="{x:Static SystemColors.HighlightTextColor}"
/>
</ListBox.Resources>
</ListBox>
1 "Older" meaning old enough to vote.
And if your version of .NET predates the existence of SystemColors.InactiveSelectionHighlightTextBrushKey, this could probably use some refinement, but it works:
<ListBox
>
<ListBox.ItemContainerStyle>
<Style
TargetType="ListBoxItem"
BasedOn="{StaticResource {x:Type ListBoxItem}}"
>
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid
Background="{TemplateBinding Background}"
>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter
Property="Background"
Value="{StaticResource {x:Static SystemColors.HighlightBrushKey}}"
/>
<Setter
Property="Foreground"
Value="{StaticResource {x:Static SystemColors.HighlightTextBrushKey}}"
/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True" />
<Condition Property="IsEnabled" Value="False" />
</MultiTrigger.Conditions>
<Setter
Property="Background"
Value="{StaticResource {x:Static SystemColors.InactiveCaptionBrushKey}}"
/>
</MultiTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Some time ago i asked here how to bind the expanded event to the viewmodel and came to a solution using AttachedCommandBehavior:
<TreeView Name="tv" ItemsSource="{Binding TreeViewElements}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children, Mode=OneTime}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Title, Mode=OneTime}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="local:CommandBehavior.Event" Value="Expanded"></Setter>
<Setter Property="local:CommandBehavior.Command" Value="{Binding DataContext.ExpandCommand, ElementName=tv}"></Setter>
<Setter Property="local:CommandBehavior.CommandParameter" Value="{Binding}"></Setter>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Now it is necessary to bind the collapsed event, too.
Adding this to the style section like this does not work, only the collapsed event is binded:
<Setter Property="local:CommandBehavior.Event" Value="Expanded"></Setter>
<Setter Property="local:CommandBehavior.Command" Value="{Binding DataContext.ExpandCommand, ElementName=tv}"></Setter>
<Setter Property="local:CommandBehavior.CommandParameter" Value="{Binding}"></Setter>
<Setter Property="local:CommandBehavior.Event" Value="Collapsed"></Setter>
<Setter Property="local:CommandBehavior.Command" Value="{Binding DataContext.CollapseCommand, ElementName=tv}"></Setter>
<Setter Property="local:CommandBehavior.CommandParameter" Value="{Binding}"></Setter>
Then i found an example on the AttachedCommandBehavior homepage to use a collection of behaviors:
<local:CommandBehaviorCollection.Behaviors>
<local:BehaviorBinding Event="MouseLeftButtonDown" Action="{Binding DoSomething}" CommandParameter="An Action on MouseLeftButtonDown"/>
<local:BehaviorBinding Event="MouseRightButtonDown" Command="{Binding SomeCommand}" CommandParameter="A Command on MouseRightButtonDown"/>
</local:CommandBehaviorCollection.Behaviors>
The problem is that adding such a collection in the style section does not work, visual studio gives the error that the behavior property can not be attached to style.
Has anybody an idea how i can bind both events to the viewmodel?
Do it like this:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="local:CommandBehaviorCollection.Behaviors">
<Setter.Value>
<local:BehaviorBinding Event="MouseLeftButtonDown" Action="{Binding DoSomething}" CommandParameter="An Action on MouseLeftButtonDown"/>
<local:BehaviorBinding Event="MouseRightButtonDown" Command="{Binding SomeCommand}" CommandParameter="A Command on MouseRightButtonDown"/>
</Setter.Value>
</Setter>
</Style>
Can you please give me a hint, to make HasItems Property better.
I have a TreeView like this:
<TreeView ItemsSource="{Binding Customers}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding HasItems, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding HasItems, RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
</Style.Triggers>
<Setter Property="AutomationProperties.AutomationId" Value="{Binding AutomationId}" />
<Setter Property="IsExpanded" Value="True" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Customers}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding FamilyName}" Margin="5,0,0,0" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Code behind:
public ObservableCollection<Customer> Customers { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
Customers = new ObservableCollection<Customer>();
var homer = new Customer("Homer", "Simpson");
homer.Customers.Add(new Customer("Bart", "Simpson"));
homer.Customers.Add(new Customer("Lisa", "Simpson"));
homer.Customers.Add(new Customer("Maggie", "Simpson"));
var chief = new Customer("Chief", "Wiggum");
chief.Customers.Add(new Customer("Ralf", "Wiggum"));
Customers.Add(homer);
Customers.Add(chief);
}
The Class Customer implements INotifyPropertyChanged and everything is fine.
As you see here, I have a DataTrigger to change color depending on "HasItems" Property of the TreeViewItem.
The problem is: HasItems is true, even if all children are Hidden or Collapsed.
See here: I made the VISIBILITY of son of "Chief Wiggum" Collapsed. And "Chief Wiggum"-TreeViewItem is still red.
Well, as you could figure out, the fact the items are hidden doesn't mean the tree view has no items.
One possible approach is changing your DataTrigger in the following way:
<DataTrigger Binding="{Binding Items, Converter={StaticResource HasVisibleItemsConverter}, RelativeSource={RelativeSource Self}}" Value="True">
Create a HasVisibleItemsConverter converter class that implements IValueConverter, there you should check if there are any items that are visible - I'll leave that for your own exercise.
Then you create an instance of HasVisibleItemsConverter in the Resources area (either Window.Resources or UserControl.Resources):
<Window.Resources>
<conv:HasVisibleItemsConverter x:Key="HasVisibleItemsConverter" />
</Window.Resources>
And don't forget to add conv="..." in the namespace definition for your Window/UserControl pointing to the assembly and namespace where your converter is.
I have a problem with the user control I created. This control consists of a search textbox and a treeview. The treeview shows different data templates for different node types. So I created the usercontrol with a dependency property of type datatemplate which can be bound when using my control. Inside the control, the treeview binds to the dependency property. But sadly the treeviewtemplate selector doesn't get called.
<UserControl x:Class="yyy.yyy.yyy.UI.UserControls.SearchableTreeView.SearchableTreeView"
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:behaviours="clr-namespace:yyy.yyy.yyy.UI.Behaviours;assembly=yyy"
mc:Ignorable="d"
x:Name="parent"
d:DesignHeight="300" d:DesignWidth="300">
<DockPanel DataContext="{Binding ElementName=parent}">
<TextBox DockPanel.Dock="Top" Margin="5" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"/>
<TreeView DockPanel.Dock="Top" Margin="5" ItemsSource="{Binding TreeViewItems}" ItemTemplateSelector="{Binding TreeViewTemplateSelector}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="behaviours:TreeViewItemBehaviour.IsBroughtIntoViewWhenSelected" Value="true"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<DataTrigger Binding="{Binding Path=IsVisible}" Value="false">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</DockPanel>
The code behind with the dependency property looks like that:
public partial class SearchableTreeView : UserControl
{
public SearchableTreeView()
{
InitializeComponent();
}
public static readonly DependencyProperty TreeViewTemplateSelectorProperty = DependencyProperty.Register(
"TreeViewTemplateSelector", typeof (DataTemplateSelector), typeof (SearchableTreeView), new PropertyMetadata(default(DataTemplateSelector)));
public DataTemplateSelector TreeViewTemplateSelector
{
get { return (DataTemplateSelector) GetValue(TreeViewTemplateSelectorProperty); }
set { SetValue(TreeViewTemplateSelectorProperty, value); }
}
}
And the usercontrol is used in a xaml like that:
<searchableTreeView:SearchableTreeView TreeViewTemplateSelector="{StaticResource TreeViewFieldTemplateSelector}"/>
Where the TreeViewFieldTemplateSelector is a class of type datatemplateselector, which allready worked before i startet to create a usercontrol out of the searchable treeview.
Does anybody know what I'm doing wrong? Or is it not possible to bind a datatemplateselector directly to a treeview?
Thanks
Manuel
You are complicating your system by using a DataTemplateSelector. While it is true that these objects were created for this purpose, there is a much easier way to achieve your requirements. Basically, if you declare a DataTemplate for each data type without specifying the x:Key values, then they will be applied implicitly to all objects of the correct type:
<DataTemplate DataType="{x:Type YourPrefix:YourDataType">
...
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:YourOtherDataType">
...
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:SomeOtherDataType">
...
</DataTemplate>
Now, if you put items of these data types into a collection and data bind that to a collection control, then you'll see your various items rendered as expected, but without the complications of the DataTemplateSelector.
UPDATE >>>
Ok, then try this instead... first remove the DataContext="{Binding ElementName=parent}" setting and then add a RelativeSource Binding for the TreeViewTemplateSelector property:
<DockPanel>
<TextBox DockPanel.Dock="Top" Margin="5" Text="{Binding SearchText,
UpdateSourceTrigger=PropertyChanged}"/>
<TreeView DockPanel.Dock="Top" Margin="5" ItemsSource="{Binding TreeViewItems}"
ItemTemplateSelector="{Binding TreeViewTemplateSelector, RelativeSource={
RelativeSource AncestorType={x:Type YourPrefix:SearchableTreeView}}}">
<TreeView.ItemContainerStyle>
...
</TreeView.ItemContainerStyle>
</TreeView>
</DockPanel>
I've got an observable collection of strings to thats data bound to my XAML contextmenu:
The ViewModel-Property:
public ObservableCollection<string> Indexes
{
get { return _Indexes; }
private set
{
if (value != _Indexes)
{
_Indexes = value;
OnPropertyChanged("Indexes");
}
}
}
The XAML code:
<viewmodel:IndexViewModel x:Key="IndexViewModel" />
<ContextMenu x:Key="ContextMenu_Index" Placement="Mouse" IsOpen="False">
<ContextMenu.ItemsSource>
<CompositeCollection>
<MenuItem Header="No items!" IsEnabled="False" Visibility="Collapsed">
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource IndexViewModel}, Path=Indexes.Count}" Value="0">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
<CollectionContainer Collection="{Binding Path=Indexes, Source={StaticResource IndexViewModel}}" />
</CompositeCollection>
</ContextMenu.ItemsSource>
<ContextMenu.Style>
<Style TargetType="ContextMenu"></Style>
</ContextMenu.Style>
<ContextMenu.ItemTemplate>
<DataTemplate DataType="string">
<TextBlock Text="{Binding}" MouseDown="TextBlock_Index_MouseDown"></TextBlock>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
Now I want to show the "No items" menu item if the count of Indexes is 0. But unfortunately it doesn't work this way, the "No items!" menu item is not shown. Do you have some hints?
There is a Dependency Property Setting Precedence List and because of that when you manually set Visibility it has priority over style trigger. Bring default value as setter into your Style instead of setting it against MenuItem and then Style.Trigger will be able to change that value:
<MenuItem Header="No items!" IsEnabled="False">
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource IndexViewModel}, Path=Indexes.Count}" Value="0">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
In my opinion, displaying a MenuItem to say 'No items!' is unhelpful and incorrect... surely your users can tell when there are no MenuItems without being told that. Even if you feel that you absolutely do have to do that, then why don't you simply add an actual item into your data bound collection?:
Indexes.Add("No items!");
In your AddItem method, you'd just need to check for the existence of this item before adding a new item:
if (Indexes.Contains("No items!")) Indexes.Remove("No items!");
Indexes.Add(newItem);
In your comment, you said that you couldn't Style this item differently... I don't know why you'd want to do that anyway, but you could just use the DataTemplateSelector Class to do that for you. It would easier for you to implement your requirements this way.