Display TextBlock permanently or on MouseOver - wpf

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;
}

Related

How to bind to tree view item to IsSelectionActive property

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.

How to change HierarchicalDataTemplate?

How can I change HierarchicalDataTemplate by DataTrigger in my TreeView?
<TreeView Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
ItemsSource="{Binding Nodes}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Type}" Value="group">
<Setter Property="Content">
<Setter.Value>
<TextBlock Text="{Binding Title}" />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="page">
<Setter Property="Content">
<Setter.Value>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Page.Name}" />
<TextBlock Text="{Binding Page.Format}" />
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
In ItemSource of TreeView i put list of Node objects:
public class Node
{
public string Title { get; set; }
public Page Page { get; set; }
public string Type { get; set; }
public List<Node> Nodes { get; set; } = new List<Node>();
}
And this is the result:
What am I doing wrong?
In WPF, the "correct" way to create complex content is with a template.
I tried the XAML below, and it worked. Where I bind Content="{Binding}" on the inner ContentControl, that just binds the content of the contentcontrol to the DataContext of the parent -- in this case, the Node object.
<TreeView
Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
ItemsSource="{Binding Nodes}"
>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<ContentControl
x:Name="PART_ContentControl"
Content="{Binding}"
/>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Type}" Value="Group">
<Setter Property="ContentTemplate" TargetName="PART_ContentControl">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="Page">
<Setter Property="ContentTemplate" TargetName="PART_ContentControl">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Page.Name}" />
<TextBlock Text="{Binding Page.Format}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Changing background color for a ComboBox, it's not changing color at all

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.

Using styles within a data template

I have a class called item, and it contains just two properties. I will display them on the screen as buttons with a style. This question relates to how I can style the button based on the IsSelected value, when the element I want to affect is in the style not the data template. I have already tried with a Trigger but been unable to get it to work.
The class is below.
public class Item : ObservableObject
{
private string _title;
private bool _isSelected;
public string Title
{
get { return _title; }
set
{
_title = value;
RaisePropertyChanged("Title");
}
}
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
}
I use a data template to display these items in a ItemsControls.
<ItemsControl ItemsSource="{Binding Path=Items}" ItemTemplate="{StaticResource ResourceKey=ItemTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Using the following style and data template.
<Style x:Key="ItemButton" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Name="ButtonBorder" BorderThickness="2,2,2,0" BorderBrush="#AAAAAA" CornerRadius="6,6,0,0" Margin="2,20,0,0" Background="Black">
<ContentPresenter
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="ItemTemplate">
<Button Height="60" Style="{StaticResource ItemButton}" Name="Button">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Title}"
HorizontalAlignment="Left" Margin="5,5,5,3" FontSize="25" Foreground="#6B6B6B" FontFamily="Arial" />
<Button Style="{StaticResource NoChromeButton}" Margin="0,0,5,0">
<Button.Content>
<Image Height="20" Source="/WpfApplication1;component/Image/dialogCloseButton.png"></Image>
</Button.Content>
<Button.ToolTip>
Close
</Button.ToolTip>
</Button>
</StackPanel>
</Button>
</DataTemplate>
I need to change the Background of "ButtonBorder" from Black to White when IsSelected is True, on the object Item.
I have added in a Trigger in the Data Template
This does not work, I guess its because the style overrides the DataTemplate, thus the background stays white. Yet when i try to do a trigger in the style, I can't access the property IsSelected?
DataTemplate Trigger
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="Button" Property="Background" Value="White"/>
</DataTrigger>
</DataTemplate.Triggers>
Style trigger
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" Value="White"/>
</DataTrigger>
</Style.Triggers>
Am i missing something?
Make your ButtonBorder.Background be {TemplateBinding Background}, which means it will use whatever background color is assigned to the templated Button, then you can change your Button's background based on a Trigger
<Style x:Key="ItemButton" TargetType="Button">
<Setter Property="Background" Value="Black" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Name="ButtonBorder" Background="{TemplateBinding Background}" ... >
<ContentPresenter ... "/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SelectableItemButton" TargetType="Button" BasedOn="{StaticResource ItemButton}">
<Setter Property="Background" Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" Value="White"/>
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="ItemTemplate">
<Button Height="60" Style="{StaticResource SelectableItemButton}">
...
</Button>
</DataTemplate>
I'm also making a SelectableItemButton Style which inherits from ItemButton, and just implements the trigger
Shouldn't the target be "ButtonBorder" instead of "Button" in :
<Setter TargetName="Button"....
Also, to access the property IsSelected you need to set the TargetType in the style ....

WPF Trigger binding to MVVM property

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);
}
}

Resources