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>
Related
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}"/>
I've merged two programs, and now there are two DataContexts in my code. One is DataContext = this; and the other is DataContext = _uiData;. I'd like to keep the former, because there are many bindings relying on this DataContext (not shown in my code, though). For the latter, I changed it to textBox.Text = _uiData.ToString();, but it doesn't show anything. How would you take care of this kind of situation?
Here is my code:
MainWindow.xaml.cs
using Both_ListBox_n_TextBox_Binding.ViewModel;
using System.Collections.ObjectModel;
using System.Windows;
namespace Both_ListBox_n_TextBox_Binding
{
public partial class MainWindow : Window
{
public ObservableCollection<GraphViewModel> RightListBoxItems { get; }
= new ObservableCollection<GraphViewModel>();
private UISimpleData _uiData = new UISimpleData();
public MainWindow()
{
InitializeComponent();
RightListBoxItems.Add(new GraphViewModel("T1"));
RightListBoxItems.Add(new GraphViewModel("T2"));
RightListBoxItems.Add(new GraphViewModel("T3"));
DataContext = _uiData; // How can I show this?
//textBox.Text = _uiData.ToString(); // This doesn't show anything
DataContext = this; // I'd like to KEEP this
//RightListBox.ItemsSource = RightListBoxItems; // Works, but not for this time
}
}
}
MainWindow.xaml
<Window x:Class="Both_ListBox_n_TextBox_Binding.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:Both_ListBox_n_TextBox_Binding"
mc:Ignorable="d"
Title="MainWindow" Height="253.5" Width="297.5">
<Grid>
<ListBox x:Name="RightListBox"
ItemsSource="{Binding RightListBoxItems}" DisplayMemberPath="Text"
SelectionMode="Extended" Margin="20,20,20,100"/>
<TextBox Margin="100,0,0,40" HorizontalAlignment="Left" VerticalAlignment="Bottom" Height="22" Width="100"
Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent,
RelativeSource={RelativeSource Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Grid>
</Window>
ViewModel/UISimpleData.cs
using System;
using System.ComponentModel;
namespace Both_ListBox_n_TextBox_Binding.ViewModel
{
public class UISimpleData : INotifyPropertyChanged, IDataErrorInfo
{
private double _doubleField = 5.5;
public double DoubleField
{
get
{
return _doubleField;
}
set
{
if (_doubleField == value)
return;
_doubleField = value;
RaisePropertyChanged("DoubleField");
}
}
public string this[string propertyName]
{
get
{
string validationResult = null;
switch (propertyName)
{
case "DoubleField":
{
if (DoubleField < 0.0 || DoubleField > 10.0)
validationResult = "DoubleField is out of range";
break;
}
default:
throw new ApplicationException("Unknown Property being validated on UIData");
}
return validationResult;
}
}
public string Error { get { return "Not Implemented"; } }
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}
ViewModel/GraphViewModel.cs
namespace Both_ListBox_n_TextBox_Binding.ViewModel
{
public class GraphViewModel
{
public string Text { get; }
public GraphViewModel(string text) => Text = text;
}
}
Thank you in advance.
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>
Please let me know if I am doing something wrong in my code. I am trying to bind a WPF menu to a "MenuViewModel". The binding works as I expect in a non styled Window.
I am using MahApps.Metro for styling purposes only and this is how it looks after binding.
Here's the link to the source code http://sdrv.ms/W5uJpY
ViewModel:
public class Menu : INotifyPropertyChanged
{
public Menu()
{
IsEnabled = true;
Children = new List<Menu>();
}
#region [ Menu Properties ]
private bool _isEnabled;
private string _menuText;
private ICommand _command;
private IList<Menu> _children;
public string MenuText
{
get { return _menuText; }
set
{
_menuText = value;
RaisePropertyChanged("MenuText");
}
}
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
RaisePropertyChanged("IsEnabled");
}
}
public ICommand Command
{
get { return _command; }
set
{
_command = value;
RaisePropertyChanged("Command");
}
}
public IList<Menu> Children
{
get { return _children; }
set
{
_children = value;
}
}
#endregion
#region [INotifyPropertyChanged]
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
XAML:
<Menu Grid.Row ="0" IsMainMenu="True" x:Name="mainMenu" VerticalAlignment="Top" ItemsSource="{Binding Children}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
<!--Or can be the line below, both yield the same result-->
<!--<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MetroMenuItem}">-->
<!--NOTICE THAT SUB MENU's of OPEN work fine-->
<Setter Property="Header" Value="{Binding Path=MenuText}"/>
<Setter Property="Command" Value="{Binding Path=Command}"/>
<Setter Property="ItemsSource" Value="{Binding Path=Children}"/>
</Style>
</Menu.ItemContainerStyle>
</Menu>
I think I found an answer here
http://karlshifflett.wordpress.com/2008/02/03/wpf-sample-series-databound-hierarchicaldatatemplate-menu-sample/
It does the binding correctly - along with maintaining Separators.
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.