Bind Events to UserControl in WPF - c#

How can i propagate the PreviewKeyDown event out from the myUserControl which is inside a myWindow.
in myWindow.xaml
<local:MyFilter x:Name="check" MyEvent="submit" />
in myUserContorl.xaml
<ComboBox x:Name="combo" PreviewKeyDown="{Binding Path=MyEvent}" />
in myUserContorl.xaml.cs
#region MyEvent
/// <summary>
/// Gets or sets the Label which is displayed next to the field
/// </summary>
public EventHandler MyEvent
{
get { return (EventHandler)GetValue(EventHandlerProperty); }
set { SetValue(EventHandlerProperty, value); }
}
/// <summary>
/// Identified the Label dependency property
/// </summary>
public static readonly DependencyProperty EventHandlerProperty =
DependencyProperty.Register("MyEvent", typeof(EventHandler),
typeof(myUserControl), new PropertyMetadata(""));
#endregion
This works for just 'string fields' like content or text... but doesn't work for events

Just add (and remove) event handlers to the underlying control:
public event KeyEventHandler MyEvent
{
add { combo.AddHandler(PreviewKeyDownEvent, value); }
remove { combo.RemoveHandler(PreviewKeyDownEvent, value); }
}
without any Binding in XAML:
<ComboBox x:Name="combo" />

Related

WPF MVVM How to pick up text changes on Button Click

I am working on a WPF project with MVVM and I encountered a problem of not picking up the last text change when I click the save button.
My components are setup like this:
A toolbar view with toolbar VM, which has the save button. button
click event is bound to a Icommand.
A form view with a bunch of text fields, bound to a sepreate VM.
the toolbar view and form view are in seperate files.
all my VMs inherits from BindableBase (part of the Prism.MvvM package)
On button click, I will take the values from the form VM and save them... simple and stragight forward.
All is well, except that upon editing the last field, clicking the save button does not trigger the lose focus event. Therefore, the property Set event is not triggered. I will have to click away from the fields after editing then click on the button.
Here's my code:
public class ViewModel: BindableBase
{
private string _someText;
public string SomeText
{
get { return _someText; }
set { SetProperty(ref _someText, value); }
}
}
in the view XAML:
<TextBox Text="{Binding SomeText}"/>
in the toolbar XAML:
<Button Command="{Binding SaveCommand}" ToolTip="Save">
</Button>
ViewModel for the toolbar:
public class ToolbarViewModel : BindableBase
{
private ICommand _saveCommand;
public ICommand SaveCommand
{
get
{
return _saveCommand ?? (_saveCommand = new BaseCommandHandler(() => {
//Save code
}, () => true));
}
}
}
code for BaseCommandHandler:
public class BaseCommandHandler : ICommand
{
private Action _action;
private Func<bool> _canExecute;
/// <summary>
/// Creates instance of the command handler
/// </summary>
/// <param name="action">Action to be executed by the command</param>
/// <param name="canExecute">A bolean property to containing current permissions to execute the command</param>
public BaseCommandHandler(Action action, Func<bool> canExecute)
{
_action = action;
_canExecute = canExecute;
}
/// <summary>
/// Wires CanExecuteChanged event
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// Forcess checking if execute is allowed
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public bool CanExecute(object parameter)
{
return _canExecute.Invoke();
}
public void Execute(object parameter)
{
_action();
}
}
Does anyone know a clean way to make sure focus is lost or a way to trigger the set event of the bindable properties
You should use UpdateSourceTrigger=PropertyChanged
<TextBox Text="{Binding TextValue, UpdateSourceTrigger=PropertyChanged}"/>

How to show TreeView on ComboBox.SelectedItem != null

I am writing a Directory explorer using the MVVM-Pattern.
My UI consists of a ComboBox which contains a path to a directory and a TreeView which acts as a Directory explorer.
The thing I am struggling with is to show the TreeView only when a ComboBox item is selected but I have no real idea how to achieve that.
Here is my code.
MainWindow.xaml
<StackPanel>
<TextBlock TextWrapping="Wrap" Text="Select a Repository Directory" Margin="10,0" FontSize="16"/>
<ComboBox x:Name="cmbx" Margin="10,30,10,0" ItemsSource="{Binding CmbxItems}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}"/>
<TreeView x:Name="FolderView" Height="250" Margin="10,50,10,0" ItemsSource="{Binding Items}" Visibility="{Binding IsItemSelected}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Name="img" Width="20" Margin="5"
Source="{Binding Type,
Converter={x:Static local:HeaderToImageConverter.ConverterInstance}}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
ViewModel.cs
/// <summary>
/// ViewModel for the main Directory view.
/// </summary>
class ViewModel : BaseViewModel
{
#region Properties
public ObservableCollection<DirectoryStructureViewModel> Items { get; set; }
public ObservableCollection<string> CmbxItems { get; set; }
public bool _itemIsSelected = false;
public bool ItemIsSelected
{
get
{
return _itemIsSelected;
}
set
{
_itemIsSelected = value;
}
}
public string _selectedItem;
public string SelectedItem
{
get
{
return _selectedItem;
}
set
{
ItemIsSelected = true;
MessageBox.Show("Selection changed");
_selectedItem = value;
}
}
#endregion
#region Constructor
/// <summary>
/// Constructor.
/// </summary>
public ViewModel()
{
CmbxItems = new ObservableCollection<string>(){};
CmbxItems.Add(DirectoryItem.rootPath);
//Get initial directory.
var children = DirectoryStructure.GetInitialDirectory();
//Create view models from data.
this.Items = new ObservableCollection<DirectoryStructureViewModel>(children.Select(dir => new DirectoryStructureViewModel(dir.FullPath, NodeTypes.Folder)));
}
#endregion
}
BaseViewModel.cs
/// <summary>
/// Base ViewModel that fires PropertyChanged events.
/// </summary>
public class BaseViewModel : INotifyPropertyChanged
{
//Event that is fired when aa child property changes its value.
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
}
DirectoryStructureViewModel.cs
public class DirectoryStructureViewModel : BaseViewModel
{
#region Properties
public NodeTypes Type { get; set; }
public string FullPath { get; set; }
public string Name { get { return DirectoryStructure.GetDirectoryOrFileName(this.FullPath); } }
/// <summary>
/// List of all children contained in this item.
/// </summary>
public ObservableCollection<DirectoryStructureViewModel> Children { get; set; }
/// <summary>
/// Indicates that this item can be expanded.
/// </summary>
public bool CanExpand { get { return this.Type != NodeTypes.File; } }
/// <summary>
/// Indicates if the current item is expanded.
/// </summary>
public bool IsExpanded
{
get
{
return this.Children?.Count(chldrn => chldrn != null) > 0;
}
set
{
if (value == true)
{
Expand();
}
else
{
this.ClearChildren();
}
}
}
public bool IsItemSelected { get; set; }
#endregion
#region Commands
public ICommand ExpandCommand { get; set; }
#endregion
#region Constructor
/// <summary>
/// Constructor.
/// </summary>
/// <param name="fullPath">Path of this item.</param>
/// <param name="type">Type of this item.</param>
public DirectoryStructureViewModel(string fullPath, NodeTypes type)
{
this.ExpandCommand = new TreeViewRelayCommand(Expand);
this.FullPath = fullPath;
this.Type = type;
this.ClearChildren();
}
#endregion
#region Helper Methods
//Removes all children from the list.
private void ClearChildren()
{
this.Children = new ObservableCollection<DirectoryStructureViewModel>();
if (this.Type != NodeTypes.File)
{
//Adds a dummy item to show the expand arrow.
this.Children.Add(null);
}
}
#endregion
#region Functions
/// <summary>
/// Expands this directory and finds all children.
/// </summary>
private void Expand()
{
if (this.Type != NodeTypes.File)
{
//Find all children
var children = DirectoryStructure.GetDirectoryContents(this.FullPath);
this.Children = new ObservableCollection<DirectoryStructureViewModel>(children.Select(content => new DirectoryStructureViewModel(content.FullPath, content.Type)));
}
else
{
return;
}
}
#endregion
}
Commands.cs
class TreeViewRelayCommand : ICommand
{
#region Members
private Action mAction;
#endregion
#region Events
/// <summary>
/// Event that is executed, when <see cref="CanExecute(object)"/> value has changed.
/// </summary>
public event EventHandler CanExecuteChanged = (sender, e) => { };
#endregion
#region Constructor
/// <summary>
/// Constructor.
/// </summary>
/// <param name="action"></param>
public TreeViewRelayCommand(Action action)
{
mAction = action;
}
#endregion
#region Command Methods
public bool CanExecute(object parameter)
{
return true;
}
/// <summary>
/// Executes the commands action.
/// </summary>
/// <param name="parameter"></param>
public void Execute(object parameter)
{
mAction();
}
#endregion
}
Edit: I am using FodyWeavers
You are almost there:
<TreeView ... Visibility="{Binding IsItemSelected}">
One problem is that you are binding Visibility to bool. There should be an binding error in Outputs window (check it now and always for various possible problems with WPF applications).
You can use BoolToVisibilityConverter (using this answer):
<someContainer.Resources>
<BooleanToVisibilityConverter x:Key="converter" />
</someContainer.Resources>
...
<TreeView ... Visibility="{Binding IsItemSelected, Converter={StaticResource converter}}">
Backing fields shouldn't be public.
You should (normally) rise notifications for all properties used in bindings. Typically at the end of setter.
I'd personally use getter only property:
string _selectedItem;
public string SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsItemSelected));
}
}
public bool IsItemSelected => SelectedItem != null;
Also you miss correct event rising method:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// can be public if you want to rise event from outside
protected void OnPropertyChanged([CallerMemberName] string property = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}

Adding traditional events to a custom control

I have been trying to figure this out with lots of googling and SO, but unfortunately I cannot solve this issue. The more I read, the more confused I get.
I would like to build an autocomplete textbox as a custom control.
My CustomControl:
<UserControl x:Class="ApplicationStyling.Controls.AutoCompleteTextBox"
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:ApplicationStyling.Controls"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
Name="AutoCompleteBox">
<Grid>
<TextBox Grid.Row="3"
Style="{DynamicResource InputBox}"
x:Name="SearchBox"
Text="{Binding Text}"
TextChanged="{Binding ElementName=AutoCompleteBox, Path=TextChanged}"/>
<ListBox x:Name="SuggestionList"
Visibility="Collapsed"
ItemsSource="{Binding ElementName=AutoCompleteTextBox, Path=SuggestionsSource}"
SelectionChanged="{Binding ElementName=AutoCompleteBox, Path=SelectionChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Label}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
My Code Behind:
using System.Collections;
using System.Windows;
using System.Windows.Controls;
namespace ApplicationStyling.Controls
{
/// <summary>
/// Interaction logic for AutoCompleteTextBox.xaml
/// </summary>
public partial class AutoCompleteTextBox : UserControl
{
public static readonly DependencyProperty SuggestionsSourceProperty;
public static readonly DependencyProperty TextProperty;
// Events
public static readonly RoutedEvent TextChangedProperty;
public static readonly RoutedEvent SelectionChangedProperty;
static AutoCompleteTextBox()
{
// Attributes
AutoCompleteTextBox.SuggestionsSourceProperty = DependencyProperty.Register("SuggestionsSource", typeof(IEnumerable), typeof(AutoCompleteTextBox));
AutoCompleteTextBox.TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(AutoCompleteTextBox));
// Events
AutoCompleteTextBox.TextChangedProperty = EventManager.RegisterRoutedEvent("TextChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AutoCompleteTextBox));
AutoCompleteTextBox.SelectionChangedProperty = EventManager.RegisterRoutedEvent("SelectionChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AutoCompleteTextBox));
}
#region Events
public event RoutedEventHandler TextChanged
{
add { AddHandler(TextChangedProperty, value); }
remove { RemoveHandler(TextChangedProperty, value); }
}
// This method raises the Tap event
void RaiseTextChangedEvent()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(AutoCompleteTextBox.TextChangedProperty);
RaiseEvent(newEventArgs);
}
public event RoutedEventHandler SelectionChanged
{
add { AddHandler(SelectionChangedProperty, value); }
remove { RemoveHandler(SelectionChangedProperty, value); }
}
// This method raises the Tap event
void RaiseSelectionChangedEvent()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(AutoCompleteTextBox.SelectionChangedProperty);
RaiseEvent(newEventArgs);
}
#endregion
#region DProperties
/// <summary>
/// IEnumerable ItemsSource Property for the Suggenstion Box
/// </summary>
public IEnumerable SuggestionsSource
{
get
{
return (IEnumerable)GetValue(AutoCompleteTextBox.SuggestionsSourceProperty);
}
set
{
SetValue(AutoCompleteTextBox.SuggestionsSourceProperty, value);
}
}
/// <summary>
/// This is the Text attribute which routes to the Textbox
/// </summary>
public string Text
{
get
{
return (string)GetValue(AutoCompleteTextBox.TextProperty);
}
set
{
SetValue(AutoCompleteTextBox.TextProperty, value);
}
}
#endregion
public AutoCompleteTextBox()
{
InitializeComponent();
SearchBox.TextChanged += (sender, args) => RaiseTextChangedEvent();
SuggestionList.SelectionChanged += (sender, args) => RaiseSelectionChangedEvent();
}
}
}
And lastly, the way I use it:
<asc:AutoCompleteTextBox x:Name="ShareAutoCompleteBox"
Grid.Row="3"
SelectionChanged="ShareAutoCompleteBox_SelectionChanged"
TextChanged="ShareAutoCompleteBox_TextChanged"/>
where asc is the namespace for the outsourced class library which is loaded via app.xaml.
Anyways, the issues I am getting in the XAML at the TextBox.TextChanged attribute, and when running the code:
System.InvalidCastException: Unable to cast object of type 'System.Reflection.RuntimeEventInfo' to type 'System.Reflection.MethodInfo'.
So what exactly is going on here? I would like to forward the AutoCompleteTextBox TextChanged to the TextBox within the Custom Control Template. Same with the SelectionChanged to the Listbox.
I took most of the code from either https://msdn.microsoft.com/en-us/library/ms752288(v=vs.100).aspx (for the events) and from some other SO questions the code for the custom properties.
Not sure, what the problem is and I am looking forward to your help.
The exception is happening because you are trying to bind the value of the TextChanged field to an attribute that expects a method reference. It's really confusing to WPF. :)
Just remove the TextChanged attribute from the TextBox element in your XAML:
TextChanged="{Binding ElementName=AutoCompleteBox, Path=TextChanged}"
You already subscribe to the event in your constructor, which is enough. If you do want to use the TextChanged attribute instead of subscribing in the constructor, then you can do that, but you need to provide an actual event handler, e.g. a method in the code-behind. That method would just call the RaiseTextChangedEvent() method, just as your current event handler does. It's just that it would be a named method in the class instead of an anonymous method declared in the constructor.
Same thing applies to the other event.
That said, you might reconsider implementing the forwarded events at all. Typically, your control's Text property would be bound to the property of some model object, which can itself react appropriately when that bound property changes. It shouldn't need a separate event on the UserControl object to tell it that its value has changed.

Update TextBlock with binding not working

I have a question about databinding!
I am writing code for a 'node editor' that has some (different) nodes in it.
I use a BaseViewModel class that derives from INotifyPropertyChanged.
There is a 'base' NodeViewModel (that derives from it) with an ObservableCollection and other Properties, like the Node's Name property. It's implementation looks like this:
(in public class NodeViewModel : BaseViewModel):
protected String mName = String.Empty;
public String Name {
get { return mName; }
set {
if (mName == value) {
return;
}
mName = value;
OnPropertyChanged("Name");
}
}
With an OnPropertyChanged handler that looks like this:
(in BaseViewModel)
protected virtual void OnPropertyChanged(string propertyName) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Now I have one additional RoomViewModel that derives from NodeViewModel.
I use another different ViewModel that I call RoomCollectionViewModel to group some rooms.
Now when I add a room to my roomcollection (by drawing a connection between them) I test all connected rooms for the same name.
If an already connected room exists in the collection with the same room name (e.g. "new room") I want to change those two room's names to e.g. "new room #1" and "new room #2". No problem so far.
Every node control (created using DataTemplates with set DataContext set to the ViewModel) contains a TextBlock (a modified one) that displays the node's name.
This is where it gets problematic:
I use a modified Textblock because I want to be able to modify the node's name by double-clicking on it. And that works perfectly, only if I modify the RoomViewModel's name in Code, this (modified) TextBlock won't update.
The strange thing is this:
When two equally named rooms in a collection get renamed by my code and I then double-click on the editable TextBlock (which converts to a TextBox in that process), I already see the modified Text. So I assume my DataBinding and my code is correct, just not complete :)
So how is it possible to force an update of my EditableTextBlock, the Text (DependencyProperty) seems to be updated correctly...
I hope you understand what my problem is! Thank you for any help.
Update 1
This is the XAML code for my EditableTextBlock (it comes from here: http://www.codeproject.com/Articles/31592/Editable-TextBlock-in-WPF-for-In-place-Editing)
<UserControl x:Class="NetworkUI.EditableTextBlock"
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:NetworkUI"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="240" x:Name="mainControl">
<UserControl.Resources>
<DataTemplate x:Key="EditModeTemplate">
<TextBox KeyDown="TextBox_KeyDown" Loaded="TextBox_Loaded" LostFocus="TextBox_LostFocus"
Text="{Binding ElementName=mainControl, Path=Text, UpdateSourceTrigger=PropertyChanged}"
Margin="0" BorderThickness="1" />
</DataTemplate>
<DataTemplate x:Key="DisplayModeTemplate">
<TextBlock Text="{Binding ElementName=mainControl, Path=FormattedText}" Margin="5,3,5,3" MouseDown="TextBlock_MouseDown" />
</DataTemplate>
<Style TargetType="{x:Type local:EditableTextBlock}">
<Style.Triggers>
<Trigger Property="IsInEditMode" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource EditModeTemplate}" />
</Trigger>
<Trigger Property="IsInEditMode" Value="False">
<Setter Property="ContentTemplate" Value="{StaticResource DisplayModeTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
And here is the code-behind file:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace NetworkUI {
/// <summary>
/// Interaction logic for EditableTextBlock.xaml
/// </summary>
public partial class EditableTextBlock : UserControl {
#region Dependency Properties, Events
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(String), typeof(EditableTextBlock),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static readonly DependencyProperty IsEditableProperty =
DependencyProperty.Register("IsEditable", typeof(Boolean), typeof(EditableTextBlock), new PropertyMetadata(true));
public static readonly DependencyProperty IsInEditModeProperty =
DependencyProperty.Register("IsInEditMode", typeof(Boolean), typeof(EditableTextBlock), new PropertyMetadata(false));
public static readonly DependencyProperty TextFormatProperty =
DependencyProperty.Register("TextFormat", typeof(String), typeof(EditableTextBlock), new PropertyMetadata("{0}"));
#endregion ///Dependency Properties, Events
#region Variables and Properties
/// <summary>
/// We keep the old text when we go into editmode
/// in case the user aborts with the escape key
/// </summary>
private String oldText;
/// <summary>
/// Text content of this EditableTextBlock
/// </summary>
public String Text {
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
/// <summary>
/// Is this EditableTextBlock editable or not
/// </summary>
public Boolean IsEditable {
get { return (Boolean)GetValue(IsEditableProperty); }
set { SetValue(IsEditableProperty, value); }
}
/// <summary>
/// Is this EditableTextBlock currently in edit mode
/// </summary>
public Boolean IsInEditMode {
get {
if (IsEditable)
return (Boolean)GetValue(IsInEditModeProperty);
else
return false;
}
set {
if (IsEditable) {
if (value)
oldText = Text;
SetValue(IsInEditModeProperty, value);
}
}
}
/// <summary>
/// The text format for the TextBlock
/// </summary>
public String TextFormat {
get { return (String)GetValue(TextFormatProperty); }
set {
if (value == "")
value = "{0}";
SetValue(TextFormatProperty, value);
}
}
/// <summary>
/// The formatted text of this EditablTextBlock
/// </summary>
public String FormattedText {
get { return String.Format(TextFormat, Text); }
}
#endregion ///Variables and Properties
#region Constructor
/// <summary>
/// Default constructor for the editable text block
/// </summary>
public EditableTextBlock() {
InitializeComponent();
Focusable = true;
FocusVisualStyle = null;
}
#endregion ///Constructor
#region Methods, Functions and Eventhandler
/// <summary>
/// Invoked when we enter edit mode
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event arguments</param>
void TextBox_Loaded(object sender, RoutedEventArgs e) {
TextBox txt = sender as TextBox;
/// Give the TextBox input focus
txt.Focus();
txt.SelectAll();
}
/// <summary>
/// Invoked when we exit edit mode
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event arguments</param>
void TextBox_LostFocus(object sender, RoutedEventArgs e) {
IsInEditMode = false;
}
/// <summary>
/// Invoked when the user edits the annotation.
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event arguments</param>
void TextBox_KeyDown(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
IsInEditMode = false;
e.Handled = true;
}
else if (e.Key == Key.Escape) {
IsInEditMode = false;
Text = oldText;
e.Handled = true;
}
}
/// <summary>
/// Invoked when the user double-clicks on the textblock
/// to edit the text
/// </summary>
/// <param name="sender">Sender (the Textblock)</param>
/// <param name="e">Event arguments</param>
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e) {
if (e.ClickCount == 2)
IsInEditMode = true;
}
#endregion ///Methods, Functions and Eventhandler
}
Thank you for any help!
Update 2
I changed the following line of code:
<TextBlock Text="{Binding ElementName=mainControl, Path=FormattedText}" Margin="5,3,5,3" MouseDown="TextBlock_MouseDown" />
to:
<TextBlock Text="{Binding ElementName=mainControl, Path=Text}" Margin="5,3,5,3" MouseDown="TextBlock_MouseDown" />
and now it is working!
I didn't see the TextBlock using the FormattedText in the first place! Ugh, thank you very much, now everything updates perfectly!
As postes by Lee O. the problem was indeed the bound property from my EditableTextBlock control.
The TextBlock used the FormattedText property that was updated by my Binding. Now I use the Text property for both, the TextBlock and the TextBox controls.
I simply removed the FormattedText property as well as the TextFormatProperty (DependencyProperty) and TextFormat property from my EditableTextBlock because I didn't plan to use those.
Thank you again!

Bindings inside WPF user control

In order to get into the WPF world and getting used to bindings, I've made a user control used to define a search filter. Depending on the wanted filter, the user can either enter a text, pick a date or select an item in a combo box. Here's an example with three instances of the created search control, each being of different type:
The good news is, everything is working but I'm not sure if everything has been done as intended.
SearchUserControl.xaml:
<UserControl x:Class="Zefix.View.UserControls.SearchUserControl"
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"
mc:Ignorable="d"
d:DesignHeight="82"
d:DesignWidth="300"
Height="Auto"
x:Name="SearchUserControlRoot">
<Grid>
<StackPanel>
<Label Name="LabelHeaderText" Content="{Binding HeaderText, ElementName=SearchUserControlRoot}" />
<TextBox Name="TextBoxSearchText" Text="{Binding SearchValue, ElementName=SearchUserControlRoot}" Visibility="{Binding TextBoxVisiblity, ElementName=SearchUserControlRoot}" />
<DatePicker Name="DatePickerSearch" SelectedDate="{Binding SearchValue, ElementName=SearchUserControlRoot}" Visibility="{Binding DatePickerVisiblity, ElementName=SearchUserControlRoot}" />
<ComboBox Name="ComboBoxSearch" Text="{Binding SearchValue, ElementName=SearchUserControlRoot}" ItemsSource="{Binding AvailableValues, ElementName=SearchUserControlRoot}" Visibility="{Binding ComboBoxVisiblity, ElementName=SearchUserControlRoot}" IsEditable="True" />
</StackPanel>
</Grid>
</UserControl>
SearchUserControl.xaml.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using Zefix.DataAccess;
namespace Zefix.View.UserControls {
/// <summary>
/// Interaction logic for SearchUserControl.xaml
/// </summary>
public partial class SearchUserControl {
#region Public Dependency Properties
/// <summary>
/// The search value property
/// </summary>
public static readonly DependencyProperty SearchValueProperty =
DependencyProperty.Register("SearchValue", typeof (object), typeof (SearchUserControl));
/// <summary>
/// The available values property
/// </summary>
public static readonly DependencyProperty AvailableValuesProperty =
DependencyProperty.Register("AvailableValues", typeof (IEnumerable<object>), typeof (SearchUserControl));
/// <summary>
/// The search type property
/// </summary>
public static readonly DependencyProperty SearchTypeProperty =
DependencyProperty.Register("SearchType", typeof (SearchType), typeof (SearchUserControl));
/// <summary>
/// The header text property
/// </summary>
public static readonly DependencyProperty HeaderTextProperty =
DependencyProperty.Register("HeaderText", typeof (string), typeof (SearchUserControl));
#endregion
#region Private Dependency Properties
/// <summary>
/// The combo box visiblity property
/// </summary>
private static readonly DependencyProperty ComboBoxVisiblityProperty =
DependencyProperty.Register("ComboBoxVisiblity", typeof (Visibility), typeof (SearchUserControl));
/// <summary>
/// The text box visiblity property
/// </summary>
private static readonly DependencyProperty TextBoxVisiblityProperty =
DependencyProperty.Register("TextBoxVisiblity", typeof (Visibility), typeof (SearchUserControl));
/// <summary>
/// The date picker visiblity property
/// </summary>
private static readonly DependencyProperty DatePickerVisiblityProperty =
DependencyProperty.Register("DatePickerVisiblity", typeof (Visibility), typeof (SearchUserControl));
#endregion
#region Public Properties
/// <summary>
/// Gets or sets the type of the search.
/// </summary>
/// <value>
/// The type of the search.
/// </value>
public SearchType SearchType {
get { return (SearchType) GetValue(SearchTypeProperty); }
set { SetValue(SearchTypeProperty, value); }
}
/// <summary>
/// Gets or sets the header text.
/// </summary>
/// <value>
/// The header text.
/// </value>
public string HeaderText {
get { return (string) GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
/// <summary>
/// Gets or sets the available values.
/// </summary>
/// <value>
/// The available values.
/// </value>
public IEnumerable<object> AvailableValues {
get { return (IEnumerable<object>) GetValue(AvailableValuesProperty); }
set { SetValue(AvailableValuesProperty, value); }
}
/// <summary>
/// Gets or sets the search value.
/// </summary>
/// <value>
/// The search value.
/// </value>
public object SearchValue {
get { return GetValue(SearchValueProperty); }
set { SetValue(SearchValueProperty, value); }
}
#endregion
#region Private Properties
/// <summary>
/// Gets or sets the combo box visiblity.
/// </summary>
/// <value>
/// The combo box visiblity.
/// </value>
private Visibility ComboBoxVisiblity {
get { return (Visibility) GetValue(ComboBoxVisiblityProperty); }
set { SetValue(ComboBoxVisiblityProperty, value); }
}
/// <summary>
/// Gets or sets the date picker visiblity.
/// </summary>
/// <value>
/// The date picker visiblity.
/// </value>
private Visibility DatePickerVisiblity {
get { return (Visibility) GetValue(DatePickerVisiblityProperty); }
set { SetValue(DatePickerVisiblityProperty, value); }
}
/// <summary>
/// Gets or sets the text box visiblity.
/// </summary>
/// <value>
/// The text box visiblity.
/// </value>
private Visibility TextBoxVisiblity {
get { return (Visibility) GetValue(TextBoxVisiblityProperty); }
set { SetValue(TextBoxVisiblityProperty, value); }
}
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="SearchUserControl" /> class.
/// </summary>
public SearchUserControl() {
InitializeComponent();
DependencyPropertyDescriptor pd = DependencyPropertyDescriptor.FromProperty(SearchTypeProperty, typeof (SearchUserControl));
pd.AddValueChanged(this, OnSearchTypePropertyChanged);
// Initialize default parameters
SearchType = SearchType.Unknown;
}
#endregion
#region Private Methods
/// <summary>
/// Called when the search type property has changed.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
private void OnSearchTypePropertyChanged(object sender, EventArgs e) {
// Hide all editors
DatePickerVisiblity = Visibility.Collapsed;
ComboBoxVisiblity = Visibility.Collapsed;
TextBoxVisiblity = Visibility.Collapsed;
// Make the correct editor visible
switch (SearchType) {
case SearchType.Date:
DatePickerVisiblity = Visibility.Visible;
break;
case SearchType.TextSelection:
ComboBoxVisiblity = Visibility.Visible;
break;
case SearchType.Text:
TextBoxVisiblity = Visibility.Visible;
break;
}
}
#endregion
}
}
Instantiation of the search controls from the parent control:
<ribbon:Tab Label="Search">
<ribbon:Group Padding="0,5,0,5">
<customcontrols:SearchUserControl x:Name="SearchUserControlCompanyName" HeaderText="company name" Margin="5,0,0,0" SearchType="Text" VerticalAlignment="Center" VerticalContentAlignment="Center" />
<customcontrols:SearchUserControl x:Name="SearchUserControlCompanyNationality" HeaderText="company nationality (ISO3 code)" Margin="5,0,0,0" SearchType="TextSelection" AvailableValues="{Binding Path=CompaniesViewModel.ISO3Codes}" VerticalAlignment="Center" />
<customcontrols:SearchUserControl x:Name="SearchUserControlDateFounded" HeaderText="date founded" Margin="5,0,0,0" SearchType="Date" VerticalAlignment="Center" VerticalContentAlignment="Center" />
<ribbon:Button Context="StatusBarItem" Name="ButtonApplyFilter" Label="Search" ImageSourceSmall="/Resources/search_magnifying_glass_find.png" Margin="5,0,0,0" VerticalAlignment="Center" Click="OnButtonApplyFilterClicked" Command="{Binding Path=ApplyFilterCommand}" ScreenTipHeader="Apply the search filter" VerticalContentAlignment="Center" VariantSize="Large" />
</ribbon:Group>
</ribbon:Tab>
In the SearchControl I wanted to display the correct component (textbox, datepicker or combobox) according to the set SearchType. For this the, xxxVisibility dependency properties and properties have been created (they are being set when the SearchTypeProperty notifies a property changed event). As there is no reason to expose them as public (they are being used only inside the SearchControl), I've made them private; MSDN states that bound properties MUST be public though. The project compiles and runs without an issue, but errors are being shown for the bound xxxVisibility properties with the message 'Public member expected' (can't tell if it's visual studio or resharper telling me that).
Is my approach to create this user control correct in respect to the WPF concepts?
Should the xxxVisibility properties be public (event though I don't want to expose them)?
This is a very difficult question to 'answer', rather than just 'comment' on. In my personal opinion, your UserControl has been written well and as far as I can see doesn't break any rules. Although I don't see any problem with declaring a private DependencyProperty, it is unusual. In this situation, developers often chose to implement a public Read Only DependencyProperty with a private DependencyPropertyKey instead:
private static readonly DependencyPropertyKey ComboBoxVisiblityPropertyKey
= DependencyProperty.RegisterReadOnly("ComboBoxVisiblity", typeof(int),
typeof(SearchUserControl), new PropertyMetadata(Visibility.Collapsed));
public static readonly DependencyProperty ComboBoxVisiblityProperty
= ComboBoxVisiblityPropertyKey.DependencyProperty;
public int ComboBoxVisiblity
{
get { return (int)GetValue(ComboBoxVisiblityProperty); }
protected set { SetValue(ComboBoxVisiblityPropertyKey, value); }
}
Some developers may also think it unusual that you are creating properties of type Visibility rather than binding bool values with BoolToVisibilityConverters, but again... that is your prerogative. Overall, well done! :)

Categories

Resources