I have a WPF app where the view models are in a .Net Standard library so they can be reused in mobile apps. This means the view models do not have access to the actual controls. I have a tree view that uses a DataTemplate to define the tree view item. I then have a ContentControl which displays a vectored image. The Fill of the vectored image is data bound to the view model. The problem is that I need to change colors when the tree view item is no longer focused but still selected. How can my view model data bind to the tree view item focused property? After more research it seems I need to bind from the control to my viewmodel one way to IsSelectionActive however the TreeViewItem style does not permit binding to the IsSelectionActive stating there is no setter.
<DataTemplate x:Key="NormalTemplate">
<StackPanel
Orientation="Horizontal"
ToolTip="{Binding Tooltip}"
Opacity="{Binding Opacity}">
<ContentControl Template="{StaticResource TreeViewItemImage}" Focusable="False"/>
<fa:ImageAwesome
Style="{StaticResource TreeNodeIsWaitingForResult}"
Visibility="{Binding IsRunning, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Hidden}"/>
<TextBlock Text="{Binding Text}" Style="{StaticResource TreeViewItemTextBlockStyle}" />
<StackPanel.ContextMenu>
<ContextMenu
ItemContainerStyle="{StaticResource ContextMenuItemStyle}"
ItemsSource="{Binding MenuItemVMs}"
Visibility="{Binding MenuItemVisibility, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Hidden}"/>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
I have made the following changes per comments but receive the following error when attempting to load the vector image resource style:
System.Windows.Markup.XamlParseException: 'A 'Binding' cannot be set on the 'Property' property of type 'Trigger'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.'
Here is my updated style:
<Style x:Key="HostsTreeNodeStyle" TargetType="{x:Type ContentControl}" BasedOn="{StaticResource TreeNodeStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<Viewbox Style="{StaticResource TreeNodeViewboxStyle}">
<Canvas Width="24" Height="24">
<Path Data="M4,1H20A1,1 0 0,1 21,2V6A1,1 0 0,1 20,7H4A1,1 0 0,1 3,6V2A1,1 0 0,1 4,1M4,9H20A1,1 0 0,1 21,10V14A1,1 0 0,1 20,15H4A1,1 0 0,1 3,14V10A1,1 0 0,1 4,9M4,17H20A1,1 0 0,1 21,18V22A1,1 0 0,1 20,23H4A1,1 0 0,1 3,22V18A1,1 0 0,1 4,17M9,5H10V3H9V5M9,13H10V11H9V13M9,21H10V19H9V21M5,3V5H7V3H5M5,11V13H7V11H5M5,19V21H7V19H5Z" />
</Canvas>
</Viewbox>
<ControlTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="True"/>
<Condition Binding="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Foreground" Value="{Binding Color1}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="True"/>
<Condition Binding="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Foreground" Value="{Binding Color2}"/>
</MultiDataTrigger>
<Trigger Property="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="false">
<Setter Property="Foreground" Value="{Binding Color3}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can use TreeViewItem.IsSelected and UIElement.IsKeyboardFocusWithin to create a MultiDataTrigger for these conditions.
Here's an example which uses this approach to change the Text of a TextBlock if it is inside a TreeViewItem which is selected, but not in focus:
public partial class MainWindow : Window
{
public List<FooItem> Items { get; } = new List<FooItem> { new FooItem() { Name = "A" },
new FooItem() { Name = "B" },
new FooItem() { Name = "C" },
new FooItem() { Name = "D" }};
}
public class FooItem
{
public string Name { get; set; }
public string SelectedButNotFocusedName => Name + "*";
}
<Window x:Class="CSharpTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<DockPanel>
<TreeView ItemsSource="{Binding Items}" DockPanel.Dock="Left">
<TreeView.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Name}"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="True"/>
<Condition Binding="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Text" Value="{Binding SelectedButNotFocusedName}"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<TextBox/>
</DockPanel>
</Window>
The TextBox at the end is only there as another element that you can give focus to see that the code works.
You can expose your alternate colors as a property of your view model and then use a setup like the above to switch between them in the UI.
Related
I have the Rectangle and below it the TextBlock.
I would like to display the TextBlock permanently when Permanent is selected in the ComboBox
And when Hover is selected in theComboBox, I want to display the TextBlock only in the MouseOver of the Rectangle.
(of course this is an example to illustrate another problem I have)
<StackPanel HorizontalAlignment="Center">
<ComboBox ItemsSource="{Binding Modes}"/>
<Rectangle x:Name="Rectangle" Width="20" Height="20" Fill="Yellow"/>
<TextBlock x:Name="TextBlock" Text="ABC" />
</StackPanel>
public ObservableCollection<Mode> Modes { get; set; } = new() { Mode.Hover, Mode.Permanent };
public enum Mode
{
Hover, Permanent
}
I would use a multidatatrigger to handle this.
<StackPanel HorizontalAlignment="Center">
<ComboBox ItemsSource="{Binding Modes}"
SelectedItem="{Binding SelectedMode}"
/>
<Rectangle x:Name="Rectangle"
Width="20" Height="20" Fill="Yellow"/>
<TextBlock x:Name="TextBlock" Text="ABC">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=IsMouseOver, ElementName=Rectangle}" Value="False" />
<Condition Binding="{Binding Path=SelectedMode}" Value="{x:Static local:Mode.Hover}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Visibility" Value="Collapsed" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
My viewmodel looks like:
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private ObservableCollection<Mode> modes = new() { Mode.Hover, Mode.Permanent };
[ObservableProperty]
private Mode selectedMode = Mode.Permanent;
}
I have a TextBlock and I have a MultiDataTrigger defined as, if both properties are false, then I want the TextBlock to move to an other grid column. Problem is the entire style doesn't work at all. It doesn't even set the default Grid.Column to 5. Please help. Making an edit to the post, the first two textblocks are collapsed and shown correctly
<TextBlock
Name="PlateBarcodeTextBlock"
Grid.Column="3"
Text="{Binding Barcode}"
Visibility="{Binding ShowBarCodeForPlate,
Converter={StaticResource boolToVisibility}}" />
<!--Plate Size-->
<TextBlock
Name="PlateSizeTextBlock"
Grid.Column="4"
Style="{StaticResource PlateSizeDisplayStyle}"
Visibility="{Binding ShowSelectedPlateSize,
Converter={StaticResource boolToVisibility}}"/>
<TextBlock
Name="ProtocolNameTextBlock"
Text="{Binding ProtocolName}" >
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Grid.Column" Value="5"></Setter>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ShowBarCodeForPlate}"
Value="False"/>
<Condition Binding="{Binding ShowSelectedPlateSize}"
Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Grid.Column" Value="3"></Setter>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
I'am trying to create a trigger which removes a TextBlock from the tab navigation when the Visibility is Collapsed.
This is the style:
<Style x:Uid="Style_1" TargetType="TextBlock">
<Setter x:Uid="Setter_1" Property="TextOptions.TextFormattingMode" Value="{StaticResource TextFormattingMode}"/>
<Setter x:Uid="Setter_32" Property="TextOptions.TextRenderingMode" Value="{StaticResource TextRenderingMode}"/>
<Setter x:Uid="Setter_2" Property="TextBlock.FontFamily" Value="{StaticResource FontFamily}"/>
<Setter x:Uid="Setter_3" Property="SnapsToDevicePixels" Value="True"/>
<Setter x:Uid="Setter_4" Property="VerticalAlignment" Value="Center"/>
<Setter x:Uid="Setter_74" Property="UseLayoutRounding" Value="True"/>
<Setter x:Uid="Setter_5" Property="Foreground" Value="{StaticResource LabelForeground}"/>
<Setter x:Uid="Setter_7" Property="FontSize" Value="{StaticResource FontSize}"/>
<Style.Triggers>
<DataTrigger x:Uid="DataTrigger_2" Binding="{Binding Source={x:Static cs:ZoomLevel.Instance}, Path=ActualZoomLevelIsDefault}" Value="False">
<Setter x:Uid="Setter_33" Property="TextOptions.TextFormattingMode" Value="Ideal"/>
</DataTrigger>
<DataTrigger x:Uid="DataTrigger_100" Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Visibility}" Value="Collapsed">
<!--<Setter Property="IsEnabled" Value="False"/>-->
<!--<Setter Property="Control.IsTabStop" Value="False"/>-->
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
But the TextBlock still gains the focus if I tab to it. IsKeyboardFocusWithin is true when I focus to the TextBlock. I can set IsEnabled to false in my trigger to make it work, but I wonder why the attached property is not working. So my question is: why is the KeyboardNavigation property not working?
Example:
<TextBlock x:Uid="TextBlock_2" Grid.Column="1" Grid.Row="1" Margin="3,3,0,0" HorizontalAlignment="Right" Visibility="Collapsed">
<Hyperlink x:Uid="Hyperlink_2" Command="{Binding SelectRoutingMethods}">
<Run x:Uid="Run_2" Text="{Binding ContactPreferences}"/>
</Hyperlink>
</TextBlock>
The TextBlock is tabable but still visible in my example.
It's not your TextBlock that gets the focus. It's Hyperlink. I don't know why, it's not even in Visual Tree but it does. It seems like a bug. If you set its property Focusable to false it wont take focus anymore.
Here is complete simplified example:
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="Focusable" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Visibility}" Value="Collapsed">
<Setter Property="Focusable" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox>Temp</TextBox>
<TextBlock Visibility="Visible" Grid.Row="1">
<Hyperlink Focusable="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TextBlock}, Path=Focusable}" Command="{Binding AddOptionalAddressCommand}">test</Hyperlink>
</TextBlock>
<TextBox Grid.Row="2" >Temp</TextBox>
</Grid>
Credits to #Shadowed for finding the issue with hyperlink.
Here is my alternativ solution: set the KeyboardNavigation.IsTabStop on Hyperlink based on the next UIElement visibility up the tree (which will be the TextBlock in this specific case.
<Hyperlink x:Uid="Hyperlink_2" KeyboardNavigation.IsTabStop="{Binding IsVisible,RelativeSource={RelativeSource AncestorType={x:Type UIElement}}}">
As answered by #Shadowed, it was Hyperlink that is getting the Focus. Not sure why this is happening as the Content should not get Focusif the parent is in Collapsed state.
Anyway, I can give you workaround for this.
<StackPanel>
<StackPanel.Resources>
<local:VisibilitytoFocusConverter x:Key="VisibilitytoFocusConv" />
</StackPanel.Resources>
<TextBox >Temp</TextBox>
<TextBlock Visibility="Collapsed" Height="20" KeyboardNavigation.TabNavigation ="{Binding Visibility, RelativeSource={RelativeSource Self}, Converter={StaticResource VisibilitytoFocusConv}}">
<Hyperlink Command="{Binding AddOptionalAddressCommand}" />
</TextBlock>
<TextBox >Temp</TextBox>
</StackPanel>
Converter:
public class VisibilitytoFocusConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo language)
{
return ((Visibility)value) == Visibility.Visible ? "Local" : "None";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo language)
{
return new NotFiniteNumberException();
}
}
Hope that helps.
I going crazy that I just can't change the color of the ComboBox. Have tried to use the background property right on the ComboBox but nothing happens.
Have also tried to use a Style block and set the background color, but that does also not work.
Code
<ComboBox Padding="7" Height="34" Background="#ffffff">
<ComboBox.Resources>
<Style x:Key="{x:Type ComboBox}" TargetType="ComboBox">
<Setter Property="Background" Value="red" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="black" />
</Style>
</ComboBox.Resources>
<ComboBoxItem IsSelected="True">1 - Room</ComboBoxItem>
<ComboBoxItem>2 - Rooms</ComboBoxItem>
<ComboBoxItem>3 - Rooms</ComboBoxItem>
<ComboBoxItem>4 - Rooms</ComboBoxItem>
<ComboBoxItem>5+ - Rooms</ComboBoxItem>
</ComboBox>
Even though that I have set the background color to white, It still only the standard grey color.
Here you can see how it looks:
Hope someone can tell me what I'm doing wrong?
here are several thing which in my opinion can help you:
Remove the Background definition from the ComboBox declaration(Background="#ffffff").
Move the combo items declaration to the combo holding Grid because of the fact that ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container.
Implement the data template selector to support data templates of combo (one for selected item, second for the items to select).
Here is the XAML code
<Grid>
<Grid.Resources>
<x:Array Type="{x:Type system:String}" x:Key="MyRoomsArray">
<system:String>1 - Room</system:String>
<system:String>2 - Rooms</system:String>
<system:String>3 - Rooms</system:String>
<system:String>4 - Rooms</system:String>
<system:String>5+ - Rooms</system:String>
</x:Array>
</Grid.Resources>
<ComboBox Padding="7" Height="34" SelectedIndex="0" ItemsSource="{StaticResource MyRoomsArray}">
<ComboBox.Resources>
<DataTemplate x:Key="ItemToSelect">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="Red"
BorderBrush="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBox}, Path=BorderBrush, UpdateSourceTrigger=PropertyChanged}"
BorderThickness ="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBox}, Path=BorderThickness, UpdateSourceTrigger=PropertyChanged}">
<TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Text="{Binding }" />
</Border>
</Grid>
</DataTemplate>
<DataTemplate x:Key="SelectedItem">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBox}, Path=Background, UpdateSourceTrigger=PropertyChanged}"
BorderBrush="Transparent"
BorderThickness ="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBox}, Path=BorderThickness, UpdateSourceTrigger=PropertyChanged}">
<TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Text="{Binding }" />
</Border>
</Grid>
</DataTemplate>
<wpfComboBAckground:ComboDataTemplateSelector x:Key="ComboDataTemplateSelector" Selected="{StaticResource SelectedItem}" ItemToSelect="{StaticResource ItemToSelect}"/>
<Style TargetType="ComboBox">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Background" Value="Red" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="ItemTemplateSelector" Value="{StaticResource ComboDataTemplateSelector}"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Transparent"></Setter>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="Background" Value="Red"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Resources>
</ComboBox>
</Grid>
Here is the data template selector
public class ComboDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var selected = false;
// container is the ContentPresenter
FrameworkElement fe = container as FrameworkElement;
if (fe == null) return ItemToSelect;
var cbo = fe.TemplatedParent as ComboBox;
if (cbo != null)
selected = true;
return selected ? Selected : ItemToSelect;
}
public DataTemplate Selected { get; set; }
public DataTemplate ItemToSelect { get; set; }
}
How it looks like:
Regards.
I have a datatemplate containing an image that I want to be hidden if the the value of a property in a ViewModel is true. Can anyone tell me why the the xaml below does not work?
<Image x:Name="img" Source="..\Images\List_16.png" Margin="0,0,5,0">
<Image.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentListHasPendingChanges}" Value="True">
<Setter Property="Image.Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding CurrentListHasPendingChanges}" Value="False">
<Setter Property="Image.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
Try removing "Image" part from Property="Image.Visibility" so you'll have:
<Setter Property="Visibility" Value="Hidden"/>
and add TargetType to your Style:
<Style TargetType="{x:Type Image}">
I just did something similar using a ContentControl.
<ContentControl Content="{Binding CurrentListHasPendingChanges}">
<ContentControl.ContentTemplate>
<DataTemplate>
<Image x:Name="img" Source="..\Images\List_16.png" Margin="0,0,5,0" Visibility="Hidden" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding}" Value="False">
<Setter Property="Image.Visibility" Value="Visible" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
From http://karlhulme.wordpress.com/2007/03/06/using-a-contentcontrol-and-datatemplate-to-indicate-new-andor-modified-data/
isn't that
<Setter Property="Visibility" Value="Hidden" />
?
I assume you use INotifyProptyChanged.
EDIT I did some Googling and I think you need to use some sort of template in order to make the trigger work.
eg.: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/ae2dbfb7-5dd6-4352-bfa1-53634289329d
http://www.thejoyofcode.com/Help_Why_cant_I_use_DataTriggers_with_controls_in_WPF.aspx
In my opinion we not need to use Triggers, with only the Binding it works well.
To make binding to a property model, you can use BooleanToVisibilityConverter
Is declared as follows:
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</UserControl.Resources>
And how to use it is simple, just point to the key stated above:
<Image HorizontalAlignment="Left" Height="16" VerticalAlignment="Center" Width="16"
Visibility="{Binding HasError, Converter={StaticResource BooleanToVisibilityConverter}}"
Source="/myPath;component/Resources/Images/image1.png"/>
The property in the ViewModel:
private bool _hasError = false;
public bool HasError
{
get { return !string.IsNullOrEmpty(_messageError); }
set
{
_hasError = value;
this.NotifyOfPropertyChange(() => this.HasError);
}
}