Propagate event from custom control to form - c#

I have a custom control like:
public sealed class BorderEx : Control
{
public static readonly RoutedEvent ReloadClickEvent = EventManager.RegisterRoutedEvent("ReloadClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(BorderEx));
public event RoutedEventHandler ReloadClick
{
add { AddHandler(ReloadClickEvent, value); }
remove { RemoveHandler(ReloadClickEvent, value); }
}
void RaiseReloadClickEvent()
{
var newEventArgs = new RoutedEventArgs(ReloadClickEvent);
RaiseEvent(newEventArgs);
}
static BorderEx()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BorderEx), new FrameworkPropertyMetadata(typeof(BorderEx)));
}
}
and there is event rised on reloadButton click at generic.xaml
<ControlTemplate TargetType="{x:Type cbr:BorderEx}">
<ControlTemplate.Triggers>
<Trigger SourceName="reloadButton" Property="IsPressed" Value="True">
<Setter TargetName="reloadButton" Property="Background" Value="Green"/>
<EventSetter Event="ReloadClick" Handler="RaiseReloadClickEvent"></EventSetter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate
But I have no idea how to raise this external event from internal button. Spend last few hours googling and ended up with nothing. above EventSetter is not working.

This part was ok,
public static readonly RoutedEvent ReloadClickEvent = EventManager.RegisterRoutedEvent("ReloadClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(BorderEx));
public event RoutedEventHandler ReloadClick
{
add { AddHandler(ReloadClickEvent, value); }
remove { RemoveHandler(ReloadClickEvent, value); }
}
static BorderEx()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BorderEx), new FrameworkPropertyMetadata(typeof(BorderEx)));
}
I had to create dependency property, so I can change some value on click event
public static readonly DependencyProperty ReloadProperty = DependencyProperty.Register("Reload", typeof (bool), typeof (BorderEx), new PropertyMetadata(default(bool), ReloadClicked));
public bool Reload
{
get { return (bool) GetValue(ReloadProperty); }
set { SetValue(ReloadProperty, value); }
}
and I can handle it in additional method triggered on change
private static void ReloadClicked(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
if (!((bool) e.NewValue)) return;
var sender = (BorderEx) o;
sender.RaiseEvent(new RoutedEventArgs(ReloadClickEvent));
}
then it just changing value on click was needed
<ControlTemplate.Triggers>
<Trigger SourceName="reloadButton" Property="IsPressed" Value="True">
<Setter TargetName="reloadButton" Property="Background" Value="Green"/>
<Setter Property="Reload" Value="True"/>
</Trigger>
</ControlTemplate.Triggers>

Your event looks great, but the fact that EventSetter can not be set in the trigger. Quote from link:
Because using EventSetter to wire up event handler is a compile-time feature which is plumbed through IStyleConnector interface, there is another interface called IComponentConnector which is used by the XAML compiler to wire up event handler for standalone XAML elements.
You can do this. Identify EventSetter outside trigger, such as in the early Style / Template:
<Style TargetType="{x:Type local:BorderEx}">
<EventSetter Event="Button.Click" Handler="ReloadClickEvent" />
...
</Style>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ReloadClickEvent(object sender, RoutedEventArgs e)
{
RaiseEvent(new DemoEventArgs(BorderEx.ReloadClickEvent, sender));
}
}
public class DemoEventArgs : RoutedEventArgs
{
public DemoEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source)
{
MessageBox.Show("Raise!");
}
}
public sealed class BorderEx : Control
{
public static readonly RoutedEvent ReloadClickEvent = EventManager.RegisterRoutedEvent("ReloadClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(BorderEx));
public event RoutedEventHandler ReloadClick
{
add { AddHandler(ReloadClickEvent, value); }
remove { RemoveHandler(ReloadClickEvent, value); }
}
private void RaiseReloadClickEvent()
{
var newEventArgs = new RoutedEventArgs(ReloadClickEvent);
RaiseEvent(newEventArgs);
}
static BorderEx()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BorderEx), new FrameworkPropertyMetadata(typeof(BorderEx)));
}
}
Or alternatively, use the DependencyProperty (can also be attached). Example:
Property definition:
public static readonly DependencyProperty SampleProperty =
DependencyProperty.RegisterAttached("Sample",
typeof(bool),
typeof(SampleClass),
new UIPropertyMetadata(false, OnSample));
private static void OnSample(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
// do something...
}
}
Calling in XAML.
In EventTrigger:
<EventTrigger SourceName="MyButton" RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MyBox" Storyboard.TargetProperty="(local:SampleClass.Sample)">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean>True</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
Using with DataTrigger (in Style/DataTemplate/etc):
<DataTrigger Binding="{Binding ElementName=MyBox, Path=Status), Mode=OneWay}" Value="True">
<Setter Property="(local:SampleClass.Sample)" Value="True" />
</DataTrigger>
Using with Trigger (in Style):
<Trigger Property="MyCheckBox.IsChecked" Value="True">
<Setter Property="(local:SampleClass.Sample)" Value="True" />
</Trigger>
Using behind code:
private void Clear_Click(object sender, RoutedEventArgs e)
{
SampleClass.SetSampleClass(MyBox, true);
}

Related

Search TextBox Control (adapted from another control) cannot fire textchanged, nor can I set or get text WPF

Here is my searchtextbox class, the actual textbox inside the XAML design is PART_TextBox, I have tried using PART_TextBox_TextChanged, TextBox_TextChanged, and OnTextBox_TextChanged, none work, and .text is empty always.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace SearchTextBox
{
[TemplatePart(Name = PartRootPanelName, Type = typeof(Panel))]
[TemplatePart(Name = PartTextBoxName, Type = typeof(TextBox))]
[TemplatePart(Name = PartSearchIconName, Type = typeof(Button))]
[TemplatePart(Name = PartCloseButtonName, Type = typeof(Button))]
public class SearchTextBox : Control
{
private const string PartRootPanelName = "PART_RootPanel";
private const string PartTextBoxName = "PART_TextBox";
private const string PartSearchIconName = "PART_SearchIcon";
private const string PartCloseButtonName = "PART_CloseButton";
// Commands.
public static readonly RoutedCommand ActivateSearchCommand;
public static readonly RoutedCommand CancelSearchCommand;
// Properties.
public static readonly DependencyProperty HandleClickOutsidesProperty;
public static readonly DependencyProperty UpdateDelayMillisProperty;
public static readonly DependencyProperty HintTextProperty;
public static readonly DependencyProperty DefaultFocusedElementProperty;
public static readonly DependencyProperty TextBoxTextProperty;
public static readonly DependencyProperty TextBoxTextChangedProperty;
static SearchTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchTextBox), new FrameworkPropertyMetadata(typeof(SearchTextBox)));
ActivateSearchCommand = new RoutedCommand();
CancelSearchCommand = new RoutedCommand();
// // Using of CommandManager.
// // https://www.codeproject.com/Articles/43295/ZoomBoxPanel-add-custom-commands-to-a-WPF-control
CommandManager.RegisterClassCommandBinding(typeof(SearchTextBox),
new CommandBinding(ActivateSearchCommand, ActivateSearchCommand_Invoke));
CommandManager.RegisterClassCommandBinding(typeof(SearchTextBox),
new CommandBinding(CancelSearchCommand, CancelSearchCommand_Invoke));
// Register properties.
HandleClickOutsidesProperty = DependencyProperty.Register(
nameof(HandleClickOutsides), typeof(bool), typeof(SearchTextBox),
new UIPropertyMetadata(true));
// // Set default value.
// // https://stackoverflow.com/questions/6729568/how-can-i-set-a-default-value-for-a-dependency-property-of-type-derived-from-dep
UpdateDelayMillisProperty = DependencyProperty.Register(
nameof(UpdateDelayMillis), typeof(int), typeof(SearchTextBox),
new UIPropertyMetadata(1000));
HintTextProperty = DependencyProperty.Register(
nameof(HintText), typeof(string), typeof(SearchTextBox),
new UIPropertyMetadata("Set HintText property"));
DefaultFocusedElementProperty = DependencyProperty.Register(
nameof(DefaultFocusedElement), typeof(UIElement), typeof(SearchTextBox));
TextBoxTextProperty =
DependencyProperty.Register(
"Text",
typeof(string),
typeof(SearchTextBox));
}
public string Text
{
get { return (string)GetValue(TextBoxTextProperty); }
set { SetValue(TextBoxTextProperty, value); }
}
public static void ActivateSearchCommand_Invoke(object sender, ExecutedRoutedEventArgs e)
{
if (sender is SearchTextBox self)
self.ActivateSearch();
}
public static void CancelSearchCommand_Invoke(object sender, ExecutedRoutedEventArgs e)
{
if (sender is SearchTextBox self)
{
self.textBox.Text = "";
self.CancelPreviousSearchFilterUpdateTask();
self.UpdateFilterText();
self.DeactivateSearch();
}
}
private static UIElement GetFirstSelectedControl(Selector list)
=> list.SelectedItem == null ? null :
list.ItemContainerGenerator.ContainerFromItem(list.SelectedItem) as UIElement;
private static UIElement GetDefaultSelectedControl(Selector list)
=> list.ItemContainerGenerator.ContainerFromIndex(0) as UIElement;
// Events.
// // Using of events.
// // https://stackoverflow.com/questions/13447940/how-to-create-user-define-new-event-for-user-control-in-wpf-one-small-example
public event EventHandler SearchTextFocused;
public event EventHandler SearchTextUnfocused;
// // Parameter passing:
// // https://stackoverflow.com/questions/4254636/how-to-create-a-custom-event-handling-class-like-eventargs
public event EventHandler<string> SearchRequested;
public event TextChangedEventHandler TextChanged;
// Parts.
private Panel rootPanel;
private TextBox textBox;
private Button searchIcon;
private Label closeButton;
// Handlers.
// Field for click-outsides handling.
private readonly MouseButtonEventHandler windowWideMouseButtonEventHandler;
// Other fields.
private CancellationTokenSource waitingSearchUpdateTaskCancellationTokenSource;
// <init>
public SearchTextBox()
{
// Click events in the window will be previewed by
// function OnWindowWideMouseEvent (defined later)
// when the handler is on. Now it's off.
windowWideMouseButtonEventHandler =
new MouseButtonEventHandler(OnWindowWideMouseEvent);
}
// Properties.
public bool HandleClickOutsides
{
get => (bool)GetValue(HandleClickOutsidesProperty);
set => SetValue(HandleClickOutsidesProperty, value);
}
public int UpdateDelayMillis
{
get => (int)GetValue(UpdateDelayMillisProperty);
set => SetValue(UpdateDelayMillisProperty, value);
}
public string HintText
{
get => (string)GetValue(HintTextProperty);
set => SetValue(HintTextProperty, value);
}
public UIElement DefaultFocusedElement
{
get => (UIElement)GetValue(DefaultFocusedElementProperty);
set => SetValue(DefaultFocusedElementProperty, value);
}
//Event handler functions.
// This would only be on whenever search box is focused.
private void OnWindowWideMouseEvent(object sender, MouseButtonEventArgs e)
{
// By clicking outsides the search box deactivate the search box.
if (!IsMouseOver) DeactivateSearch();
}
private void PART_TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
TextChanged?.Invoke(this, e);
}
public void OnTextBox_GotFocus(object sender, RoutedEventArgs e)
{
SearchTextFocused?.Invoke(this, e);
if (HandleClickOutsides)
// Get window.
// https://stackoverflow.com/questions/302839/wpf-user-control-parent
Window.GetWindow(this).AddHandler(
Window.PreviewMouseDownEvent, windowWideMouseButtonEventHandler);
}
public void OnTextBox_LostFocus(object sender, RoutedEventArgs e)
{
SearchTextUnfocused?.Invoke(this, e);
if (HandleClickOutsides)
Window.GetWindow(this).RemoveHandler(
Window.PreviewMouseDownEvent, windowWideMouseButtonEventHandler);
}
private void OnTextBox_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
CancelSearchCommand.Execute(null, this);
else if (e.Key == Key.Enter)
{
CancelPreviousSearchFilterUpdateTask();
UpdateFilterText();
}
else
{
CancelPreviousSearchFilterUpdateTask();
// delay == 0: Update now;
// delay < 0: Don't update except Enter or Esc;
// delay > 0: Delay and update.
var delay = UpdateDelayMillis;
if (delay == 0) UpdateFilterText();
else if (delay > 0)
{
// // Delayed task.
// // https://stackoverflow.com/questions/15599884/how-to-put-delay-before-doing-an-operation-in-wpf
waitingSearchUpdateTaskCancellationTokenSource = new CancellationTokenSource();
Task.Delay(delay, waitingSearchUpdateTaskCancellationTokenSource.Token)
.ContinueWith(self => {
if (!self.IsCanceled) Dispatcher.Invoke(() => UpdateFilterText());
});
}
}
}
// Public interface.
public void ActivateSearch()
{
textBox?.Focus();
}
public void DeactivateSearch()
{
// // Use keyboard focus instead.
// // https://stackoverflow.com/questions/2914495/wpf-how-to-programmatically-remove-focus-from-a-textbox
//Keyboard.ClearFocus();
if (DefaultFocusedElement != null)
{
UIElement focusee = null;
if (DefaultFocusedElement is Selector list)
{
focusee = GetFirstSelectedControl(list);
if (focusee == null)
focusee = GetDefaultSelectedControl(list);
}
if (focusee == null) focusee = DefaultFocusedElement;
Keyboard.Focus(focusee);
}
else
{
rootPanel.Focusable = true;
Keyboard.Focus(rootPanel);
rootPanel.Focusable = false;
}
}
// Helper functions.
private void CancelPreviousSearchFilterUpdateTask()
{
if (waitingSearchUpdateTaskCancellationTokenSource != null)
{
waitingSearchUpdateTaskCancellationTokenSource.Cancel();
waitingSearchUpdateTaskCancellationTokenSource.Dispose();
waitingSearchUpdateTaskCancellationTokenSource = null;
}
}
private void UpdateFilterText() => SearchRequested?.Invoke(this, textBox.Text);
// .
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// // Idea of detaching.
// // https://www.jeff.wilcox.name/2010/04/template-part-tips/
if (textBox != null)
{
textBox.GotKeyboardFocus -= OnTextBox_GotFocus;
textBox.LostKeyboardFocus -= OnTextBox_LostFocus;
textBox.KeyUp -= OnTextBox_KeyUp;
}
rootPanel = GetTemplateChild(PartRootPanelName) as Panel;
textBox = GetTemplateChild(PartTextBoxName) as TextBox;
searchIcon = GetTemplateChild(PartSearchIconName) as Button;
closeButton = GetTemplateChild(PartCloseButtonName) as Label;
if (textBox != null)
{
textBox.GotKeyboardFocus += OnTextBox_GotFocus;
textBox.LostKeyboardFocus += OnTextBox_LostFocus;
textBox.KeyUp += OnTextBox_KeyUp;
}
}
}
}
My XAML:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SearchTextBox" xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase">
<Style TargetType="{x:Type local:SearchTextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SearchTextBox}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid Name="PART_RootPanel">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="28"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<TextBox Name="PART_TextBox" Background="#FF333337" BorderThickness="0" VerticalContentAlignment="Center" Foreground="White" FontSize="14px" Text=""/>
<TextBlock IsHitTestVisible="False" Text="{TemplateBinding HintText}" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="4,0,0,0" FontSize="14px" Foreground="#FF7C7777">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=PART_TextBox}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
<Button Width="28" Grid.Column="1" Name="PART_SearchIcon" Content="🔍" Background="#FF252526"
Focusable="False" Command="{x:Static local:SearchTextBox.ActivateSearchCommand}">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid Background="#FF333337">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Button.Template>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=PART_TextBox}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Label Grid.Column="1" Width="28" HorizontalAlignment="Center" HorizontalContentAlignment="Center" Cursor="Hand" VerticalContentAlignment="Center" VerticalAlignment="Stretch" Margin="0" Padding="4" Foreground="White" FontWeight="Bold" Content="x" Name="PART_CloseButton" Focusable="False"
Background="#FF333337">
<Label.InputBindings>
<MouseBinding Command="{x:Static local:SearchTextBox.CancelSearchCommand}" MouseAction="LeftClick" />
</Label.InputBindings>
<Label.Style>
<Style TargetType="Label">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=PART_TextBox}" Value="">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#000"/>
</Trigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The control builds fine, but I cannot get searchTextBox.Text, it returns null, and on TextChanged does not fire. Any ideas?
I would suggest that you do a bit more research and learning about the code you're using before you copy and paste it. Try to understand what it's doing and why before use it.
You don't reference your method PART_TextBox_TextChanged anywhere, it's not used in OnApplyTemplate where I would expect it to be assigned as a handler. Without that, it will never be called. I would expect a textBox.TextChanged -= PART_TextBox_TextBox_TextChanged in the first if statement, and a textBox.TextChanged += PART_TextBox_TextBox_TextChanged in the second.
You never set the value of your TextBoxText dependency property anywhere; neither in code nor with a Binding or TemplateBinding. Your Text property is referencing TextBoxText for its value, which would always be null if it's never set. public static readonly DependencyProperty TextBoxTextProperty; (along with the code in static SearchTextBox()) declares that a dependency property exists, but the value of it is never set.
In a custom control like this, you can bind the Text property of PART_TextBox to your TextBoxText property using a TemplateBinding like so:
<TextBox Name="PART_TextBox" ... Text="{TemplateBinding TextBoxText}"/>

Trigger/Setter on dependency property reached by Binding

I want to set a dependency property that I declared in my Class1 that inherits from DependencyObject:
public static readonly DependencyProperty MyMouseOverProperty = DependencyProperty.Register("MyMouseOver", typeof(bool), typeof(Class1),
new PropertyMetadata(false,new PropertyChangedCallback(On_MyMouseOver)));
private static void On_MyMouseOver(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// some code here
}
public bool MyMouseOver
{
get { return (bool)GetValue(MyMouseOverProperty); }
set { SetValue(MyMouseOverProperty, value); }
}
I'll use "MyMouseOver" in XAML in order to use its state in "On_MyMouseOver" to affect another Object.
<DataTemplate DataType="{x:Type local:Class1}">
<Canvas x:Name="Canvas_Classe1"
Background="Transparent">
<Canvas.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="{Binding Path=MyMouseOver}" Value="True"/>
</Trigger>
</Canvas.Triggers>
</Canvas>
but ... this is not working.
How to use Setter to access to a DependencyProperty declared not within a control but a class ?
You can accomplish this with attached property
public class Class1
{
public static readonly DependencyProperty MyMouseOverProperty = DependencyProperty.RegisterAttached(
"MyMouseOver", typeof(bool), typeof(Class1), new FrameworkPropertyMetadata(false, PropertyChangedCallback)
);
public static void SetMyMouseOver(UIElement element, Boolean value)
{
element.SetValue(MyMouseOverProperty, value);
}
public static bool GetMyMouseOver(UIElement element)
{
return (bool)element.GetValue(MyMouseOverProperty);
}
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// some code here
}
}
Set attached property on DataTemplate IsMouseOver
<DataTemplate DataType="{x:Type local:Class1}">
<Canvas x:Name="Canvas_Classe1"
Background="Black">
</Canvas>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="local:Class1.MyMouseOver" Value="True"/>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>

TwoWay Binding Not Updating

I've added a ScrollViewer behavior that allows me to scroll to the top when a property is set to true based on Scroll the scrollviewer to top through viewmodel. I've found that this works perfectly the first time, but subsequent attempts do not fire because the TwoWay binding isn't setting my property back to false.
Here is my simple project showing my issue:
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="100" Width="200">
<ScrollViewer local:ScrollViewerBehavior.ScrollToTop="{Binding Reset, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
</Grid.RowDefinitions>
<Button VerticalAlignment="Bottom" Content="Top" Command="{Binding Scroll}"/>
</Grid>
</ScrollViewer>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ViewModel _ViewModel;
public MainWindow()
{
InitializeComponent();
DataContext = _ViewModel = new ViewModel();
}
}
}
ViewModel.cs
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace WpfApp1
{
public class RelayCommand : ICommand
{
private readonly Action<object> action;
public RelayCommand(Action<object> action)
{
this.action = action;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
action(parameter);
}
}
public class ViewModel : INotifyPropertyChanged
{
private bool _reset = false;
public ViewModel()
{
Scroll = new RelayCommand(o =>
{
Reset = true;
});
}
public event PropertyChangedEventHandler PropertyChanged;
public bool Reset
{
get { return _reset; }
set
{
bool changed = value != _reset;
_reset = value;
if (changed)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Reset)));
}
}
}
public RelayCommand Scroll { get; set; }
}
}
ScrollViewerBehavior.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfApp1
{
public static class ScrollViewerBehavior
{
public static readonly DependencyProperty ScrollToTopProperty = DependencyProperty.RegisterAttached("ScrollToTop", typeof(bool), typeof(ScrollViewerBehavior), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (o, e) =>
{
if (o is ScrollViewer sc)
{
if ((bool)e.NewValue)
{
sc.ScrollToTop();
SetScrollToTop((FrameworkElement)o, false); // this should set the property back to false
}
}
}));
public static bool GetScrollToTop(FrameworkElement o)
{
return (bool)o.GetValue(ScrollToTopProperty);
}
public static void SetScrollToTop(FrameworkElement o, bool value)
{
o.SetValue(ScrollToTopProperty, value);
}
}
}
I know that if I take out the changed check on the property, it works; however, that is not ideal for my situation. When I look at the element through WPF Inspector, I see that the property on the ScrollViewer is false as it should be, but my ViewModel property remains true.
I'm not good in English but i wrote this example. Look at this
[MainWindow.xaml.cs]
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
AnyClass c = new AnyClass();
private void h1_Click(object sender, RoutedEventArgs e)
{
Test = true;
}
private void h2_Click(object sender, RoutedEventArgs e)
{
Test = false;
}
public bool Test
{
get { return (bool)GetValue(TestProperty); }
set
{
SetValue(TestProperty, value);
c.Val = value;
}
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test", typeof(bool), typeof(MainWindow), new PropertyMetadata(false));
}
[AnyClass.cs]
class AnyClass
{
private bool val = false;
public bool Val
{
get
{
return val;
}
set
{
val = value;
}
}
}
[mainWindow.xaml]
<Button Click="h1_Click" Content="true">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=hUserControl, Path=Test}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=hUserControl, Path=Test}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Click="h2_Click" Content="false">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=hUserControl, Path=Test}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=hUserControl, Path=Test}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>

Fire method on TextChanged but not all the time

I am creating a simple AutoComplete TextBox and have a list of values which If the user starts to enter any characters in them string, the appropriate string will appear.
Now I have created my Textbox with a Binding property to my ViewModel:
<TextBox Text="{Binding ServerURL, UpdateSourceTrigger=PropertyChanged}" />
So when the user enters a new character It will trigger my property to get fired and therefore fire a method which will retrieve the values it relates to.
private string _serverURL;
public string ServerURL {
get { return _serverURL; }
set
{
_serverURL = value;
ServerURL_TextChanged();
OnPropertyChanged("ServerURL");
}
}
The method will then just populate a ListBox with the results that string refers to.
When I select a value from the ListBox i want to set the Full string value to the TextBox text property, but when i do this it triggers the method ServerURL_TextChanged().
Is there a way I can set the ServerURL property, but not to fire the method inside it?
For a solution there is a need to separate the ways with which you can set ServerURL property.
public string ServerURL {
get { return _serverURL; }
set
{
setServerURL(value, isSetByUser = true);
}
}
private function void setServerURL(string value, bool isSetByUser){
_serverURL = value;
ServerURL_TextChanged(isSetByUser);
OnPropertyChanged("ServerURL");
}
When the List is changed you can call from the code setServerURL(someValue, isSetByUser = false);
And then in ServerURL_TextChanged implementation decide what to do with it.
The easiest way to implement this functionality is to handle TextChanged events from the code-behind, where you will have full control of the UI for this kind of decision making. It does not violate MVVM principles to manage UI operations from the code-behind.
Here is an example of such a code-behind implementation. You may find it useful.
public partial class AutoCompleteComboBox : UserControl
{
private Window w;
public AutoCompleteComboBox()
{
InitializeComponent();
}
~AutoCompleteComboBox()
{
if(w == null)
{
return;
}
else
{
w.MouseLeftButtonDown -= Window_MouseLeftDown;
}
}
#region Behaviours
public void FocusTextBox()
{
txt.Focus();
txt.CaretIndex = txt.Text.Length;
}
#endregion
#region DependencyProperties
public static readonly DependencyProperty InputPaddingProperty =
DependencyProperty.Register(
"InputPadding",
typeof(Thickness),
typeof(AutoCompleteComboBox)
);
public Thickness InputPadding
{
get
{
return (Thickness)GetValue(InputPaddingProperty);
}
set
{
SetValue(InputPaddingProperty, value);
}
}
public static readonly DependencyProperty TextBoxHeightProperty =
DependencyProperty.Register(
"TextBoxHeight",
typeof(double),
typeof(AutoCompleteComboBox)
);
public double TextBoxHeight
{
get
{
return (double)GetValue(TextBoxHeightProperty);
}
set
{
SetValue(TextBoxHeightProperty, value);
}
}
public static readonly DependencyProperty ItemPanelMaxHeightProperty =
DependencyProperty.Register(
"ItemPanelMaxHeight",
typeof(double),
typeof(AutoCompleteComboBox)
);
public double ItemPanelMaxHeight
{
get
{
return (double)GetValue(ItemPanelMaxHeightProperty);
}
set
{
SetValue(ItemPanelMaxHeightProperty, value);
}
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
"ItemsSource",
typeof(IEnumerable),
typeof(AutoCompleteComboBox)
);
public IEnumerable ItemsSource
{
get
{
return (IEnumerable)ItemsSource;
}
set
{
SetValue(ItemsSourceProperty, value);
}
}
public static readonly DependencyProperty DisplayMemberPathProperty =
DependencyProperty.Register(
"DisplayMemberPath",
typeof(string),
typeof(AutoCompleteComboBox)
);
public string DisplayMemberPath
{
get
{
return GetValue(DisplayMemberPathProperty).ToString();
}
set
{
SetValue(DisplayMemberPathProperty, value);
}
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof(string),
typeof(AutoCompleteComboBox),
new FrameworkPropertyMetadata(
"",
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
)
);
public string Text
{
get
{
return GetValue(TextProperty).ToString();
}
set
{
SetValue(TextProperty, value);
}
}
public string TargetValue { get; set; } = "";
public static readonly DependencyProperty IsDropDownOpenProperty =
DependencyProperty.Register(
"IsDropDownOpen",
typeof(bool),
typeof(AutoCompleteComboBox),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
)
);
public bool IsDropDownOpen
{
get
{
return (bool)GetValue(IsDropDownOpenProperty);
}
set
{
SetValue(IsDropDownOpenProperty, value);
}
}
#endregion
#region Events
private void me_Loaded(object sender, RoutedEventArgs e)
{
w = VisualTreeHelpers.FindAncestor<Window>(this);
w.MouseLeftButtonDown += Window_MouseLeftDown;
FocusTextBox();
}
private void Window_MouseLeftDown(object sender, MouseButtonEventArgs e)
{
IsDropDownOpen = false;
}
private void lst_KeyDown(object sender, KeyEventArgs e)
{
}
private void lst_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (TargetValue != null && TargetValue.Trim().Length > 0)
{
txt.Text = TargetValue;
IsDropDownOpen = false;
}
FocusTextBox();
}
private void lst_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
}
private void lst_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (lst.SelectedItem != null)
{
TargetValue = lst.SelectedItem.ToString();
}
}
private void txt_LostFocus(object sender, RoutedEventArgs e)
{
if (lst.IsFocused == false)
{
IsDropDownOpen = false;
FocusTextBox();
}
}
private void lst_LostFocus(object sender, RoutedEventArgs e)
{
MessageBox.Show("text changed");
if (txt.IsFocused == false)
{
IsDropDownOpen = false;
}
}
private void txt_TextChanged(object sender, TextChangedEventArgs e)
{
IsDropDownOpen = true;
}
private void txt_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (IsDropDownOpen && lst.Items.Count > 0)
{
if (lst.SelectedIndex < 0)
{
lst.SelectedIndex = 0;
}
if (e.Key == Key.Up && lst.SelectedIndex > 0)
{
lst.SelectedIndex--;
}
else if (e.Key == Key.Down && lst.SelectedIndex < lst.Items.Count - 1)
{
lst.SelectedIndex++;
}
else if(e.Key == Key.Enter || e.Key == Key.Tab)
{
if(lst.SelectedIndex > -1)
{
txt.Text = TargetValue;
IsDropDownOpen = false;
FocusTextBox();
}
}
}
}
#endregion
}
And here's the XAML
<UserControl x:Class="SHARED_COMPONENTS.AutoCompleteComboBox"
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"
x:Name="me"
Loaded="me_Loaded"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<TextBox
x:Name="txt"
Background="CornflowerBlue"
Foreground="White"
Grid.Row="0"
Text="{Binding ElementName=me, Path=Text,UpdateSourceTrigger=PropertyChanged}"
TextChanged="txt_TextChanged" PreviewKeyDown="txt_PreviewKeyDown"
Height="{Binding ElementName=me, Path=ActualHeight}"
Padding="{Binding ElementName=me,Path=InputPadding}"
/>
<Popup IsOpen="{Binding ElementName=me, Path=IsDropDownOpen}" ClipToBounds="False">
<Border Grid.Row="1">
<Border.Effect>
<DropShadowEffect Color="Black" />
</Border.Effect>
<ListBox
x:Name="lst"
Grid.Row="1"
ItemsSource="{Binding ElementName=me, Path=ItemsSource}"
PreviewKeyDown="lst_KeyDown"
SelectionChanged="lst_SelectionChanged"
PreviewMouseLeftButtonDown="lst_MouseLeftButtonDown"
PreviewMouseLeftButtonUp="lst_PreviewMouseLeftButtonUp"
DisplayMemberPath="{Binding ElementName=me, Path=DisplayMemberPath }"
ClipToBounds="False"
>
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="Background" Value="#f0f0f0" />
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=lst, Path=HasItems}" Value="True" />
<Condition Binding="{Binding ElementName=me, Path=IsDropDownOpen}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible" />
</MultiDataTrigger>
<DataTrigger Binding="{Binding ElementName=me, Path=IsDropDownOpen}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="True">
<Setter Property="IsSelected" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="CornflowerBlue" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
</Border>
</Popup>
</Grid>
</Grid>
</UserControl>

WPF UserControls; triggers and changing other controls

I've created a WPF UserControl which contains a Button and a ComboBox. I'd like to change the style of both, depending on the position of the mouse, so the UIElement with the mouse over is coloured Black and the other is coloured Red. If neither are styled then the default styling will apply.
Don't worry, this nightmarish colour scheme is just to illustrate the concept!
Thanks in advance for your help.
XAML
<UserControl x:Class="WpfUserControlSample.ToolbarButtonCombo"
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:local="clr-namespace:WpfUserControlSample"
x:Name="Control"
mc:Ignorable="d"
d:DesignHeight="30">
<UserControl.Resources>
<Style TargetType="{x:Type local:ToolbarButtonCombo}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsButtonMouseOver}" Value="True">
<Setter Property="ButtonStyle" Value="Black"/>
<Setter Property="ComboStyle" Value="Red"/>
</DataTrigger>
<!--
<DataTrigger Binding="{Binding IsComboMouseOver}" Value="True">
<Setter Property="ButtonStyle" Value="Red"/>
<Setter Property="ComboStyle" Value="Black"/>
</DataTrigger>
-->
</Style.Triggers>
</Style>
</UserControl.Resources>
<StackPanel Orientation="Horizontal" Height="30">
<Button Name="btn" Background="{Binding ButtonStyle,ElementName=Control,Mode=OneWay}">
Test
</Button>
<ComboBox Name="cmb" Background="{Binding ComboStyle,ElementName=Control,Mode=OneWay}"></ComboBox>
</StackPanel>
</UserControl>
Codebehind:
namespace WpfUserControlSample
{
public partial class ToolbarButtonCombo : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public ToolbarButtonCombo()
{
InitializeComponent();
btn.MouseEnter += new MouseEventHandler(btn_MouseChanged);
btn.MouseLeave += new MouseEventHandler(btn_MouseChanged);
}
void btn_MouseChanged(object sender, MouseEventArgs e)
{
OnPropertyChanged("IsButtonMouseOver");
}
public bool IsButtonMouseOver
{
get { return btn.IsMouseOver; }
}
public static readonly DependencyProperty IsButtonMouseOverProperty =
DependencyProperty.Register("IsButtonMouseOver", typeof(string), typeof(ToolbarButtonCombo), new PropertyMetadata("false"));
public string ButtonStyle { get; set; }
public static readonly DependencyProperty ButtonStyleProperty =
DependencyProperty.Register("ButtonStyle", typeof(string), typeof(ToolbarButtonCombo));
public string ComboStyle { get; set; }
public static readonly DependencyProperty ComboStyleProperty =
DependencyProperty.Register("ComboStyle", typeof(string), typeof(ToolbarButtonCombo));
}
}
There are a two problems.
First your DataTrigger bindings do not look correct. They are looking for the IsButtonMouseOver on the DataContext, not the associated control. You'd need to use:
<DataTrigger Binding="{Binding IsButtonMouseOver, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="ButtonStyle" Value="Black"/>
<Setter Property="ComboStyle" Value="Red"/>
</DataTrigger>
Or:
<Trigger Property="IsButtonMouseOver" Value="True">
<Setter Property="ButtonStyle" Value="Black"/>
<Setter Property="ComboStyle" Value="Red"/>
</Trigger>
The other is your IsButtonMouseOver is not implemented correctly. You should do something like:
public static readonly DependencyProperty IsButtonMouseOverProperty = DependencyProperty.Register("IsButtonMouseOver",
typeof(bool), typeof(ToolbarButtonCombo), new PropertyMetadata(false));
public bool IsButtonMouseOver
{
get { return (bool)this.GetValue(IsButtonMouseOverProperty); }
set { this.SetValue(IsButtonMouseOverProperty, value); }
}
void btn_MouseChanged(object sender, MouseEventArgs e)
{
this.IsButtonMouseOver = this.btn.IsMouseOver;
}
Or even more correctly, make the IsButtonMouseOver a read-only dependency property like so:
private static readonly DependencyPropertyKey IsButtonMouseOverPropertyKey = DependencyProperty.RegisterReadOnly("IsButtonMouseOver",
typeof(bool), typeof(ToolbarButtonCombo), new FrameworkPropertyMetadata(false));
public static readonly DependencyProperty IsButtonMouseOverProperty = ToolbarButtonCombo.IsButtonMouseOverPropertyKey.DependencyProperty;
public bool IsButtonMouseOver {
get { return (bool)this.GetValue(IsButtonMouseOverProperty); }
private set { this.SetValue(IsButtonMouseOverPropertyKey, value); }
}
Your other properties (ButtonStyle and ComboStyle) would need to be properly implemented also, and their get/set methods are not backed by the dependency property.

Categories

Resources