WPF trigger for TextBox.Text doesn't work - c#

I have a dependency property in my user control called IsPromptShown:
public static DependencyProperty IsPromptShownProperty = DependencyProperty.
Register("IsPromptShown", typeof(bool), typeof(AutoCompleteSearchBox), new
PropertyMetadata(true));
public bool IsPromptShown
{
get { return (bool)GetValue(IsPromptShownProperty); }
set { SetValue(IsPromptShownProperty, value); }
}
That custom control contains a TextBox. That text box doesn't have any value assigned to its Text property:
<TextBox Name="_searchTextBox" Grid.Row="0" VerticalAlignment="Top"
GotFocus="SearchTextBox_GotFocus" LostFocus="SearchTextBox_LostFocus"
TextChanged="SearchTextBox_TextChanged"/>
Now I'm setting the following trigger in the hosting window:
<Trigger Property="IsPromptShown" Value="True">
<!--<Setter Property="FontStyle" Value="Italic"/>-->
<Setter Property="TextBox.Text" Value="Seek"/>
</Trigger>
The commented line that sets FontStyle works but the second that sets TextBox.Text doesn't. I also have been trying to set Foreground property and that also have failed. What is going on?

I had problems similar to this when I started using WPF. You just have to look at things in a different way. Instead of looking at the value of the IsPromptShown property and trying to change a property of the TextBox in a Trigger in the UserControl, do it the other way around. Look at the value of the IsPromptShown property and try to change the property of the TextBox in a Trigger in the TextBox.
<TextBox Name="_searchTextBox" Grid.Row="0" VerticalAlignment="Top"
GotFocus="SearchTextBox" LostFocus="SearchTextBox_LostFocus"
TextChanged="SearchTextBox_TextChanged">
<TextBox.Style>
<Style>
<Setter Property="TextBox.Text" Value="Default value if required" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsPromptShown, ElementName=This}"
Value="True">
<Setter Property="TextBox.Text" Value="Seek" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Note that for this to work, you will need to add Name=This to the declaration of your UserControl. This just lets the Framework know where to find the IsPromptShown property... you could just as easily use a RelativeSource Binding for this if you prefer.

I think the Problem is that you canĀ“t access the TextProperty of the Textbox in your UserControl from outside!
Try creating a Text DependencyProperty that sets the Value of the Textbox inside the UserControl and set this Property in the Trigger!

I found the solution. It is not trivial, but works. Assume that we have a custom UserControl that contains a TextBox and ather controls. We want to be capable of assigning style to each internal control depending on some bool UserControl.IsSomething dependency property. First we have to declare another dependency property Style UserControl.AlternativeStyle. Then we attach an event handler to IsSomething in order to toggle current Style and AlternativeStyle when IsSomething changed:
public static DependencyProperty AlternativeStyleProperty =
DependencyProperty.Register(
"AlternativeStyle",
typeof(Style),
typeof(MyUserControl));
public Style AlternativeStyle
{
get { return (Style)GetValue(AlternativeStyleProperty); }
set { SetValue(AlternativeStyleProperty, value); }
}
public static DependencyProperty IsSomethingProperty =
DependencyProperty.Register(
"IsSomething",
typeof(bool),
typeof(MyUserControl),
new PropertyMetadata(true, IsSomethingProperty_Changed));
public bool IsSomething
{
get { return (bool)GetValue(IsSomethingProperty); }
set { SetValue(IsSomethingProperty, value); }
}
private static void IsSomethingProperty_Changed(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
// Swap styles
// e.g.
// var tempStyle = Style;
// Style = AlternativeStyle;
// AlternativeStyle = tempStyle;
}
The harder part is about setting AlternativeStyle outside the custom UserControl. Below is how to achieve that in XAML of window that hosts our custom control:
<Window.Resources>
<Style x:Key="DefaultTextBoxStyle" TargetType="TextBox">
<Setter Property="Background" Value="Red"/>
</Style>
<Style x:Key="DefaultUserControlStyle" TargetType="local:MyUserControl">
<Style.Resources>
<Style TargetType="TextBox" BasedOn="{StaticResource DefaultTextBoxStyle}">
<Setter Property="Background" Value="Blue"/>
</Style>
</Style.Resources>
</Style>
<Style x:Key="AlternativeUserControlStyle" TargetType="local:MyUserControl" BasedOn="{StaticResource DefaultUserControlStyle}">
<Style.Resources>
<Style TargetType="TextBox" BasedOn="{StaticResource DefaultTextBoxStyle}">
<Setter Property="Background" Value="Green"/>
</Style>
</Style.Resources>
</Style>
<Style TargetType="local:MyUserControl" BasedOn="{StaticResource DefaultUserControlStyle}">
<Setter Property="AlternativeStyle" Value="{StaticResource AlternativeUserControlStyle}"/>
</Style>
</Window.Resources>
The above style will be added automaticly to all instances of MyUserControl and when we change IsSomething value, then alternative style will be applied to the owner of changed IsSomething.

Related

Wpf Style Trigger triggered only once

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

c# uwp templatebinding and dependency property

In UWP I am trying to change default template of tooltip.
I have managed it, but now I need to set tooltip arrow to point out to the control it belongs to.
My style is defined as this:
<Style TargetType="ToolTip">
<Setter Property="Foreground" Value="White" />
<Setter Property="Background" Value="{ThemeResource SystemControlBackgroundChromeMediumLowBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundChromeHighBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource ToolTipBorderThemeThickness}" />
<Setter Property="FontFamily" Value="Roboto" />
<Setter Property="FontSize" Value="{ThemeResource ToolTipContentThemeFontSize}" />
<Setter Property="Padding" Value="40,40,40,35"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Grid Background="Transparent">
<Grid
MinWidth="100"
MinHeight="90"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
Padding="15"
Background="Transparent">
And so on and on...
But now I am trying to make UserControl bind with TemplateBinding property.
I have created UserControl that have some dependecncy property.
Like this:
public PlacementMode TooltipPlacement
{
get { return (PlacementMode)GetValue(TooltipPlacementProperty); }
set { SetValue(TooltipPlacementProperty, value); CalculateArrowVisibility(); }
}
public static readonly DependencyProperty TooltipPlacementProperty =
DependencyProperty.Register("TooltipPlacement", typeof(PlacementMode), typeof(ArrowDown), null);
CalculateArrowVisibility() is a method that will calculate the arrow location depending on TooltipPlacement.
And that control is in style defined as this:
<local:ArrowDown x:Name="arrow" TooltipPlacement="{TemplateBinding Placement}"/>
But it is not bound, I have tried other TemplateProperties but no luck also.
Where is the problem here?
You are very close excpet the way you delcare the dependency property is wrong.
You should never modify its getter and setter. Instead, call your CalculateArrowVisibility method inside its property changed callback like this -
public PlacementMode TooltipPlacement
{
get => (PlacementMode)GetValue(TooltipPlacementProperty);
set => SetValue(TooltipPlacementProperty, value);
}
public static readonly DependencyProperty TooltipPlacementProperty =
DependencyProperty.Register("TooltipPlacement", typeof(PlacementMode), typeof(ArrowDown),
new PropertyMetadata(null, TooltipPlacementChangedCallback));
private static void TooltipPlacementChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var self = (ArrowDown)d;
self.CalculateArrowVisibility();
}

How to access usercontrol (toolbar) from ViewModel in MVVM?

how to talk toolbar (it is a user control) on the button to enable a wait cursor.
i have a ViewModel is inherited from viewmodelBase. But i can not use IsWorking on toolbar.
Below code is toolbar's code. i clicked select button. data is selecting from database. Cursor must be turn to wait.after Selecting, Cursor must return normal.
<Button x:Name="Select"
Content="select"
Command="{Binding SelectCommand }">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Cursor" Value="Arrow"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsWorking}" Value="True">
<Setter Property="Cursor" Value="Wait"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
ViewModelBase.cs: there is no inheritance with toolbar. it is a basemodel.
private bool _isWorking = false;
public bool IsWorking
{
get { return _isWorking; }
set
{
_isWorking = value;
OnPropertyChanged("IsWorking");
}
}
Here is the code from the view-model:
public class MainViewModel : ViewModelBase
{
public void Select()
{
IsWorking = true; cursor turn to wait mode
// db Process...
IsWorking = false; cursor turn to hand mode
}
}
How to communicate with toolbar from ViewModel? Click select Cursor must be turn Wait mode. after selection, cursor must be hand(default).
Changing the cursor in WPF sometimes works, sometimes doesn't
From what I see, your problem is that you're trying to bind from your UserControl back to the view/window in which it's located.
The usercontrol, of course, will not be able to bind like this.
You have a few options:
1 . Give the UserControl the View's datacontext:
<local:UserControl1 DataContext="{Binding ElementName=MyWindow}" />
and then in your UserControl you can bind to the ViewModel's IsWorking directly:
<DataTrigger Binding="{Binding IsWorking}" Value="True">
<Setter Property="Cursor" Value="Wait"/>
</DataTrigger>
2 .
Create a Dependency Property on your UserControl and bind to it from the view:
In your usercontrol create a new DP:
public bool MyIsWorking
{
get { return (bool)GetValue(MyIsWorkingProperty ); }
set { SetValue(MyIsWorkingProperty , value); }
}
public static readonly DependencyProperty MyIsWorkingProperty =
DependencyProperty.Register("MyIsWorking", typeof(bool), typeof(UserControl1), new UIPropertyMetadata(false));
In the usercontrol's XAML bind to the DP:
<DataTrigger Binding="{Binding MyIsWorking}" Value="True">
<Setter Property="Cursor" Value="Wait"/>
</DataTrigger>
In your window - bind the DP to the VM's IsWorking property:
<local:UserControl1 MyIsWorking="{Binding IsWorking, ElementName=MyWindow}" />
3 . Finally this will work but it's not recommended!!!**
<DataTrigger Binding="{Binding IsWorking, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" Value="True">
<Setter Property="Cursor" Value="Wait"/>
</DataTrigger>
What this does is tries to find the Window in the Visual Tree and use its DataContext. Why isn't it recommended? Because you might not be using this in a Window or you might not want it to be bound to the specific DataContext the containing Window is using. Either way, it IS another possibility.

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