Wpf Style Trigger triggered only once - c#

I have added a property trigger on a grid as below
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="ToolTip" Value=""></Setter>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="ToolTip" Value="{Binding StoredValue}"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
The property is triggered only once when I hover over the grid.
What I require is that the property's (StoredValue) getter has to be called every time when MouseHover happens.
Please help

If you really want to update the tooltip every time it is displayed, you can utilize the ToolTipOpening event to refresh the binding:
<Grid x:Name="grid1" Background="Transparent">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="ToolTip" Value="{Binding StoredValue,TargetNullValue=''}"/>
<EventSetter Event="ToolTipOpening" Handler="grid1_ToolTipOpening"/>
</Style>
</Grid.Style>
</Grid>
Update the binding in code behind:
private void grid1_ToolTipOpening(object sender, ToolTipEventArgs e)
{
var s = sender as FrameworkElement;
var be = BindingOperations.GetBindingExpressionBase(s, FrameworkElement.ToolTipProperty);
if (be != null)
{
be.UpdateTarget();
}
}
Note: the TargetNullValue='' is necessary in case StoredValue would sometimes return a null. Otherwise the Tooltip wouldn't attempt to open and thus the ToolTipOpening would never happen and the value would never update from the null to a new value.

While I can't explain the nature of problem, here is a quick workaround: rise notification manually, then binding will refresh itself. You trade trigger for event:
<Grid Background="Transparent" MouseEnter="Grid_MouseEnter">
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<!-- normal binding, this line is comment and should be gray -->
<Setter Property="ToolTip" Value="{Binding StoredValue}" />
</Style>
</Grid.Style>
</Grid>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public string StoredValue => "123"; // is called every time mouse is entered
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
// rise notification manually
void Grid_MouseEnter(object sender, MouseEventArgs e) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StoredValue)));
}

Related

Binding gets an old value out of nowhere

I have a control which derives from a TextBox and has 2 additional string properties: Formula and FormulaResult (readonly):
public class SpreadsheetCellControl : TextBox, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
public string Formula
{
get;
set;
}
public string FormulaResult
{
get
{
if (Formula != null && Formula != "")
{
string formula = Formula;
formula = formula.StartsWith("=") ? formula.Substring(1) : formula;
var calculation = Calculator.Calculate(formula);
return calculation.Result.ToString();
}
else return "";
}
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
Formula = Text;
base.OnPreviewLostKeyboardFocus(e);
}
}
As defined in XAML, Text is bound to Formula when the control is focused and FormulaResult when it is not focused:
<Style TargetType="local:SpreadsheetCellControl" BasedOn="{StaticResource HoverableStyle}">
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource self}, Path=FormulaResult, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>
<Style.Triggers>
<Trigger Property="IsFocused" Value="true">
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="Text" Value="{Binding Formula, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</Trigger>
</Style.Triggers>
</Style>
Everything works as it should. For instance, I click the control, type for example "1/2", click anywhere else and it shows "0.5".
Then I set the Formula property from code-behind to "3" (any value actually). Before I click on the control, it shows all three properties correct: 3, 3 and 3. But when I click on the control, the Text property suddenly becomes the old "1/2", which is not stored anywhere. I checked that by placing a TextBlock and binding its Text to all three values of the cell control. Debugging also showed only new values.
P.S. Also "Formula" does not seem to take values by itself from "Text" here: Setter Property="Text" Value="{Binding Formula, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
The Binding in the Text property Setter inside the Trigger is missing RelativeSource Self:
<Setter Property="Text"
Value="{Binding Path=Formula,
RelativeSource={RelativeSource Self},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>

WPF navigation and destroy current UserControl

I'm trying to build a small navigation system in my WPF application. I'm using this tutorial to navigate between pages. I want to add 'Go back' functionality on top of it for one UserControl.
I have a UserControl Orders and another UserControl Order. Orders is shown in MainWindow and when I click on an button, Order UserControl should be shown in the same place in MainWindow. I tried to put a reference to the Orders usercontrol in the Order usercontrol and navigate to the Orders through Order. But the Order isn't destroyed since I'm using a variable from that class.
How can I make sure that when I navigate to Order form Orders, the Orders isn't destroyed and when I navigate to Orders from Order, Order is destroyed.
Button click event handler in Orders Class:
private void ShowOrder(object sender, RoutedEventArgs e)
{
Order order = new Order();
Switcher.Switch(order);
}
Return back button click handler in Order Class
public UserControl parent;
private void ReturnBack(object sender, RoutedEventArgs e)
{
Switcher.Switch(parent);
}
I usually do the next pattern whice uses ControlTemplate, lets say you have in your class:
private Enums.View _currView;
public Enums.View CurrView
{
get
{
return _currView;
}
set
{
_currView = value;
OnPropertyChanged("CurrView");
}
}
When Enums.View is:
public enum View
{
ViewA = 1,
ViewB = 2,
ViewC = 3,
}
Then, using Binding to CurrView above we change the view when it changes:
<UserControl ...
xmlns:Views="clr-namespace:CustomersManager.View"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<!--*********** Control templates ***********-->
<ControlTemplate x:Key="DefultTemplate">
<Views:DefultCustomersView/>
</ControlTemplate>
<ControlTemplate x:Key="A">
<Views:ViewAllCustomersView />
</ControlTemplate>
<ControlTemplate x:Key="B">
<Views:AddNewCustomersView />
</ControlTemplate>
<ControlTemplate x:Key="C">
<Views:EditCustomersView />
</ControlTemplate>
</UserControl.Resources>
<Border BorderBrush="Gray" BorderThickness="2">
<Grid>
<ContentControl DataContext="{Binding}" >
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Template" Value="{StaticResource DefultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrView}" Value="ViewA">
<Setter Property="Template" Value="{StaticResource A}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrView}" Value="ViewB">
<Setter Property="Template" Value="{StaticResource B}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrView}" Value="ViewC">
<Setter Property="Template" Value="{StaticResource C}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl >
</Grid>
</Border>
</UserControl>

How to add a background to a textbox cue banner

This question showed me how to add a watermark text to my TextBox. I've tried to implement it in my project, but it replaces the background of my TextBox.
Because my panel has a different color, that panel color is shown through the textbox. How can I set this correctly?
I've tried to set the Background of the Label to white, but that doesn't work because it isn't stretched.
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style.Resources>
<VisualBrush x:Key="CueBannerBrush" AlignmentX="Left" AlignmentY="Center" Stretch="Uniform">
<VisualBrush.Visual>
<!-- set the background to white -->
<Label Content="Search" Foreground="LightGray" Background="White"/>
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
This gives the following:
But setting Stretch to Fill gives this result with stretched text:
This might not be the best solution, but if my controls are supposed to behave differently from what they are ment to, I like to create them myself. That way I got full control and know exactly what happens when i do this and that.
class TextBoxWaterMark : TextBox
{
#region Datafields
private string pm_WaterMark = "";
#endregion
#region Constructor
public TextBoxWaterMark()
{
}
#endregion
#region Control events
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
if ((string)this.Tag != "")
{
this.Foreground = new SolidColorBrush(Colors.Black);
this.Text = "";
}
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
if ((string)this.Tag != "")
{
if (this.Text == "")
{
this.Text = pm_WaterMark;
this.Foreground = new SolidColorBrush(Colors.Gray);
}
}
}
#endregion
#region Public get and set methods
public string WaterMark
{
get { return pm_WaterMark; }
set
{
pm_WaterMark = value;
this.Text = pm_WaterMark;
this.Foreground = new SolidColorBrush(Colors.Gray);
}
}
#endregion
and then in my XAML code i simply add it whereever i want like this.
<Form:TextBoxWaterMark WaterMark="Insert watermark text here" />
Hope this is what you are looking for :P
You can add something behind your textbox :
<StackPanel background="white">
<textbox>
</textbox>
</StackPanel>
I think that the right part of your text box is transparent, am I right ?
I also think that there are better component than a StackPanel to do this.

WPF: Launch code when IsMouseOver ComboBoxItem

I have a ComboBox. Without changing the template, is there a way that I can launch code when there user places their mouse over a ComboBoxItem, but before the selection actually occurs? It seems like I should be able to specify an EventTrigger or a Trigger to do this in the style of ComboBoxItem.
<ComboBox Grid.Column="1" Grid.Row="0"
ItemsSource="{Binding Voices}"
SelectedItem="{Binding SelectedVoice, Mode=TwoWay}">
<ComboBox.Resources>
<Style TargetType="{x:Type ComboBoxItem}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
... Launch my code from code behind... but HOW? ...
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Resources>
</ComboBox>
I'm also ok with ousing a MouseEnter, but I would rather not build a separate DataTemplate or ContentTemplate if possible.
Update. The idea behind this snippet is to Play test audio when the user hovers over a new voice, which I would have to do from the code side. Help!
You can use EventSetter:
<ComboBox.Resources>
<Style TargetType="{x:Type ComboBoxItem}">
<EventSetter Event="PreviewMouseMove" Handler="ComboBoxItem_PreviewMouseMove" />
</Style>
</ComboBox.Resources>
in code behind:
private void ComboBoxItem_PreviewMouseMove(object sender, MouseEventArgs e)
{
ComboBoxItem item = sender as ComboBoxItem;
//Now you can use this Item
}
I know a dirty solution.. just in case you run out of solutions try this as your last hope..
I tested this by creating a textblock in XAML and setting its text equal to content of comboboxitem once mouse is over it and setting text to "" once mouse has left
I am using AttachedBehaviours to find out on which particular comboboxitem is mouse over once mouse is there and also getting notified once mouse is not over it anymore or mouse is left
Try this.. create a class
public static class ComboBoxBehaviour
{
//holding reference of MainWindow class to update the textBlock
public static MainWindow windoewRef ;
public static bool GetTest(ComboBoxItem target)
{
return (bool)target.GetValue(TestAttachedProperty);
}
public static void SetTest(ComboBoxItem target, bool value)
{
target.SetValue(TestAttachedProperty, value);
}
public static readonly DependencyProperty TestAttachedProperty = DependencyProperty.RegisterAttached("Test", typeof(bool), typeof(ComboBoxBehaviour), new UIPropertyMetadata(false, OnMouseOverChanged));
static void OnMouseOverChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
ComboBoxItem item = o as ComboBoxItem;
if ((bool)e.NewValue)
{
// I am setting text of a textblock for testing once mouse is over an item
windoewRef.textBlock.Text = item.Content.ToString();
}
else
{
//setting text to "" once mouse has been moved
windoewRef.textBlock.Text = "";
}
}
}
In XAML
<TextBlock Text="" x:Name="textBlock" />
<ComboBox x:Name="combo">
<ComboBox.Resources>
<Style TargetType="{x:Type ComboBoxItem}" xmlns:behaviours="clr-namespace:WpfApplication1">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="behaviours:ComboBoxBehaviour.Test" Value="True"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="behaviours:ComboBoxBehaviour.Test" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Resources>
</ComboBox>
I know this is a bad solution and it may have problems which I haven't found yet but just my thoughts...

How can I programatically get keyboard focus on a WPF TreeViewItem?

I'm trying to programatically set keyboard focus to a tree view item (under certain conditions). I've tried 2 methods of setting focus, both of which successfully obtain focus on the TreeViewItem, but lose keyboard focus.
The tree view is bound to a view model:
<TreeView Name="solutionsModel" TreeViewItem.Selected="solutionsModel_Selected"
ItemsSource="{Binding Items, Mode=OneWay}" />
I'm trying to set focus via the TreeViewItem Selected routed event:
private void solutionsModel_Selected(object sender, RoutedEventArgs e)
{
if (solutionsModel.SelectedItem != null && solutionsModel.SelectedItem is SolutionViewModel)
{
if (e.OriginalSource != null && e.OriginalSource is TreeViewItem)
{
FocusManager.SetFocusedElement(solutionsModel, e.OriginalSource as TreeViewItem);
}
}
}
I'm trying to set focus on the TreeViewItem in the ControlTemplate:
<Style d:IsControlPart="True" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Trigger.Setters>
<Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"></Setter>
</Trigger.Setters>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true" />
<Condition Property="IsSelectionActive" Value="false" />
</MultiTrigger.Conditions>
<!--
<MultiTrigger.Setters>
<Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"></Setter>
</MultiTrigger.Setters>
-->
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Both of these methods get focus, but lose keyboard focus (TreeViewItem.IsSelectionActive is false). No other element in the window has focus or keyboard focus that I can tell (in a test, I only have one read only textbox on another panel that could get focus). Interestingly, I can get keyboard focus on the (commented out) MultiTrigger where IsSelectionActive is false, but of course that forces keyboard focus on the TreeViewItem at all times.
Is there another way to have a better chance of getting keyboard focus, and what are some conditions where keyboard focus cannot be obtained?
I'd add this as a comment if I could but, why not just have the TreeView handle the focus and work with the item abstractly using the TreeView.SelectedItem. The tree view would always be able to know which item was selected when the typing started. If an item was selected then the TreeView is in focus and you can pipe the keyboard commands through to the item.
There are probably better ways, but I found a way to do this by extending TreeView and TreeViewItem, to have a separate NeedsFocus property to trigger when to set focus.
The tree view:
<local:ModelTreeView x:Name="solutionsModel" ItemsSource="{Binding Items, Mode=OneWay}">
</local:ModelTreeView>
The updated (partial) control template:
<Style d:IsControlPart="True" TargetType="{x:Type local:ModelTreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="NeedsFocus" Value="{Binding NeedsFocus, Mode=TwoWay}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ModelTreeViewItem}">
<ControlTemplate.Triggers>
<Trigger Property="NeedsFocus" Value="true">
<Trigger.Setters>
<Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"></Setter>
</Trigger.Setters>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The extended classes:
public class ModelTreeView : TreeView
{
protected override DependencyObject GetContainerForItemOverride()
{
return new ModelTreeViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is ModelTreeViewItem;
}
}
public class ModelTreeViewItem : TreeViewItem
{
///--------------------------------------------------------------------------------
/// <summary>This property gets or sets whether the item needs focus.</summary>
///--------------------------------------------------------------------------------
public static readonly DependencyProperty NeedsFocusProperty = DependencyProperty.Register("NeedsFocus", typeof(bool), typeof(ModelTreeViewItem));
public bool NeedsFocus
{
get
{
return (bool)GetValue(NeedsFocusProperty);
}
set
{
SetValue(NeedsFocusProperty, value);
}
}
protected override DependencyObject GetContainerForItemOverride()
{
return new ModelTreeViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is ModelTreeViewItem;
}
}
In the view model, NeedsFocus is set to false whenever IsSelected is set.

Categories