I have extended the functionality of a textbox, below is part of the class with an attached property RegularExpressionEnabled:
public class AlphaNumericTextBox : TextBox
{
#region DependencyProperties
public static readonly DependencyProperty RegularExpressionEnabledProperty =
DependencyProperty.Register("RegularExpressionEnabled", typeof(bool), typeof(AlphaNumericTextBox),
new UIPropertyMetadata(false));
public bool RegularExpressionEnabled
{
get { return (bool)GetValue(RegularExpressionEnabledProperty); }
set { SetValue(RegularExpressionEnabledProperty, value); Console.WriteLine("RegularExpressionEnabled:" + (bool)value); }
}
}
This class is then incorporated into a UserControl.
XAML:
<UserControl x:Class="Libs_ViewLevel.Controls.FilterItemControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Libs_ViewLevel"
xmlns:ctrls="clr-namespace:Libs_ViewLevel.Controls"
x:Name="UserControl">
<Grid x:Name="LayoutRoot" Height="Auto" >
<ctrls:AlphaNumericTextBox x:Name="AlphaNumericTbx"
Grid.ColumnSpan="2"
Text="{Binding AlphaNumericValue}"
RegularExpressionEnabled="{Binding RegExpressionEnabled}" />
</Grid>
</UserControl>
Code-Behind:
public partial class FilterItemControl : UserControl
{
public FilterItemControl()
{
InitializeComponent();
this.DataContext = this;
}
public bool RegExpressionEnabled
{
get { return (bool)GetValue(RegExpressionEnabledProperty); }
set { SetValue(RegExpressionEnabledProperty, value); Console.WriteLine("RegExpressionEnabled:" + (bool)value); }
}
public static readonly DependencyProperty RegExpressionEnabledProperty =
DependencyProperty.Register("RegExpressionEnabled", typeof(bool), typeof(FilterItemControl),
new UIPropertyMetadata(false));
I have placed the UserControl in a Window and in the code-behind of this window, placed this statement: txtbox.RegExpressionEnabled = true;
In both RegularExpressionEnabled and RegExpressionEnabled you can see that I have placed a Console.Write().
The RegExpressionEnabled prints to the Output screen, however it does not get to the second, RegularExpressionEnabled.
Something is wrong in my binding on AlphaNumericTextBox RegularExpressionEnabled="{Binding RegExpressionEnabled}", can someone point me in the right direction?
In the code behind of FilterItemControl, RegExpressionEnabled property should be a standard property as this is what your new textbox is binding to. You don't need the dependency property in the FilterItemControl code behind either as you already have it in the new textbox.
Now instead of setting txtbox.RegExpressionEnabled = true (which actually should be txtbox.RegularExpressionEnabled = true), you set the RegExpressionEnabled property to true and the textbox will bind to this.
You will also need to raise the PropertyChanged event in the setter of the RegExpressionEnabled property to trigger the databinding.
Related
I have an application in which I set the content of a contentpresenter, dependent on the datatype by a datatemplate (see MainWindow). The Datatemplate is a usercontrol, which is actually datatype specific. (The small example below is only for demonstration, but in my "real" application the user shall be able to switch between different data.)
The usercontrol (UserControl1) has a DependencyProperty which I assign a value (in my application this is actually a binding to a VM, just set it to a string in example for simplicity).
Setting the value is still working fine. However In my UserControl I need to react to changes of the DependencyProperty to change the view of my UserControl (or later on CustomControl). So I implemented a OnPropertyChangend method.
When application starts OnPropertyChanged works as I expect it and I get the "correct" newvalue of my DependencyProperty. However, if I change my VM (i.e. my datatemplate changes) during runtime by clicking on a button, OnPropertyChanged returns the DependencyProperty's defaultvalue.
In my small example application, I can see that the value is set correctly, as the Textblock content changes to the correct value.
It only seems that OnPropertyChanged gets fired before my DependencyProperty's value gets the new value. So, it's not possible for me to react on the new value.
It is not really clear why this happens. Seems to have something to do with the order in which WPF resolves internal stuff?
Does anyone have a clue, how I can fix this behavior and get access to the current/last value when changing my VM and don't miss an update? As stated out before, I need to react on that value.
Maybe I am doing something totally stupid here. Is the approach I decided to use here a bad one? Are DataTemplates the wrong approach to switch between two pairs? What would be a better approach then? However, I guess it won't be possible to avoid the DependencyProperty and the UserControl in my application.
MainWindow.xaml
<!--MainWindow.xaml -->
<Grid>
<StackPanel>
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<ContentPresenter Content="{Binding ActiveVM}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:VM1}">
<local:UserControl1 MyProperty="Test1"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:VM2}">
<local:UserControl1 MyProperty="Test2"/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
</Grid>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
vmParent = new VMParent();
DataContext = vmParent;
var vm1 = new VM1();
var vm2 = new VM2();
}
VMParent vmParent;
private void Button_Click(object sender, RoutedEventArgs e)
{
vmParent.ChangeActiveVM();
}
}
UserControl1.xaml
<!--UserControl1.xaml -->
<TextBlock Text="{Binding MyProperty, RelativeSource={RelativeSource AncestorType={x:Type local:UserControl1}}}"/>
UserControl1.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(string), typeof(UserControl1), new PropertyMetadata("DefaultString", OnMyPropertyChangend));
private static void OnMyPropertyChangend(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == "DefaultString")
{
;
//xxxxxx
//unexpectedly i get stuck here
//Would expect/need NewValue to be Text1/Text2 to react to it
//xxxxxx
}
}
}
VMParent
class VMParent : INotifyPropertyChanged
{
public VMParent()
{
vm1 = new VM1();
vm2 = new VM2();
ActiveVM = vm1;
}
public event PropertyChangedEventHandler PropertyChanged;
VM1 vm1;
VM2 vm2;
public object ActiveVM
{
get => m_activeVM;
set { m_activeVM = value; OnPropertyChanged("ActiveVM"); }
}
private object m_activeVM;
protected internal void OnPropertyChanged(string propertyname)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
public void ChangeActiveVM()
{
if (ActiveVM is VM1)
ActiveVM = vm2;
else
ActiveVM = vm1;
}
}
VMs are only used to apply Datatemplate
class VM1
{
}
class VM2
{
}
I'm working on a "simple" case. I like to create a new custom control which implements a DependencyProperty. In the next step I like to create a binding for updating the properties in both directions. I've builded a simple sample for this case, but the binding doesn't seem to work. I've found a way for updating the DPControl's property by using the FrameworkPropertyMetadata, but I don't know whether it's also a good idea to use the OnPropertyChanged event.
HERE is my sample project:
My control contains simply a Label
<UserControl x:Class="WPF_MVVM_ListBoxMultiSelection.DPControl"
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:WPF_MVVM_ListBoxMultiSelection"
mc:Ignorable="d" Height="84.062" Width="159.641">
<Grid Margin="0,0,229,268">
<Label Content="TEST" x:Name="label" Margin="0,0,-221,-102"/>
</Grid>
</UserControl>
and implement a custom dependency property. Currently, I have also implemented the PropertyChanged method for the FramePropertyMetadata and set in this method the label's content, but I like to get it work in both directions.
public partial class DPControl : UserControl
{
public DPControl()
{
InitializeComponent();
}
public string MyCustomLabelContent
{
get { return (string)GetValue(MyCustomLabelContentProperty);}
set
{
SetValue(MyCustomLabelContentProperty, value);
}
}
private static void OnMyCustomLabelContentPropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
DPControl control = (DPControl)source;
control.label.Content = e.NewValue;
}
public static readonly DependencyProperty MyCustomLabelContentProperty = DependencyProperty.Register(
"MyCustomLabelContent",
typeof(string),
typeof(DPControl),
new FrameworkPropertyMetadata(null,
OnMyCustomLabelContentPropertyChanged
)
);
I use this control simply in a Window by:
<local:DPControl MyCustomLabelContent="{Binding MyLabelContent, Mode=TwoWay}" Margin="72,201,286,34"/>
MyLabelContent is a property in the ViewModel, which has implemented also the INotifyPropertyChanged interface.
public class ViewModel_MainWindow:NotifyPropertyChanged
{
private string _myLabelContent;
public string MyLabelContent
{
get { return _myLabelContent; }
set { _myLabelContent = value;
RaisePropertyChanged();
}
}...
So how can I get it work: Using the binding feature with my new control on custom properties.
In your UserControl:
<Label
Content="{Binding MyCustomLabelContent, RelativeSource={RelativeSource AncestorType=UserControl}}"
x:Name="label" Margin="0,0,-221,-102"/>
And get rid of that property-changed callback. All you need is the Binding.
I like to get it work in both directions
To make the dependency property two-way by default:
public static readonly DependencyProperty MyCustomLabelContentProperty =
DependencyProperty.Register(
"MyCustomLabelContent",
typeof(string),
typeof(DPControl),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
I omitted the unnecessary property change handler.
It can't usefully be two-way now, because Label.Content can't generate its own value. If you want your UserControl to set the value in its codebehind, that's easy:
MyCustomLabelContent = "Some arbitrary value";
If you did the binding like I showed you, that will update the Label in the UserControl XAML as well as the viewmodel property bound to the UserControl's dependency property.
If you want the XAML to set it, you'll need to
Lastly, this:
Margin="0,0,-221,-102"
Is not a good way to do layout. WPF layout with Grid, StackPanel, etc. is much easier and more robust.
My goal is to create add Text property to RichTextBox. I created Attached Property and set binding to ViewModel's property. Unfortunately changing text in RichTextBox doesn't update underlying property.
Here is my code:
View.cs:
public partial class KnuthMorrisPrattView : UserControl
{
public KnuthMorrisPrattView()
{
InitializeComponent();
}
public static string GetText(DependencyObject obj)
{
return (string)obj.GetValue(TextProperty);
}
public static void SetText(DependencyObject obj, string value)
{
obj.SetValue(TextProperty, value);
}
public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached
(
"Text",
typeof(string),
typeof(KnuthMorrisPrattView),
new FrameworkPropertyMetadata()
{
BindsTwoWayByDefault = true,
PropertyChangedCallback = PropertyChangedCallback,
CoerceValueCallback = CoerceValueCallback,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.LostFocus
}
);
private static object CoerceValueCallback(DependencyObject dependencyObject, object baseValue)
{
throw new NotImplementedException();
}
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
throw new NotImplementedException();
}
}
View.xaml:
<UserControl x:Class="Launcher.Views.KnuthMorrisPrattView"
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:views="clr-namespace:Launcher.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="500"
DataContext="{Binding KnuthMorrisPrattViewModel, Source={StaticResource MainViewModel}}">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="7*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<DockPanel Grid.Row="0">
<Label Content="Text" DockPanel.Dock="Top"></Label>
<RichTextBox x:Name="TextBox" views:KnuthMorrisPrattView.Text="{Binding TextToSearchArg}"/>
</DockPanel>
<DockPanel Grid.Row="1">
<Label Content="Pattern" DockPanel.Dock="Top"></Label>
<TextBox Text="{Binding PatternArg}"/>
</DockPanel>
</Grid>
ViewModel.cs:
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using GalaSoft.MvvmLight.CommandWpf;
using Launcher.Runners.KnuthMorrisPratt;
namespace Launcher.ViewModels
{
public class KnuthMorrisPrattViewModel : ViewModelBase
{
private string _textToSearchArg;
private string _patternArg;
public string TextToSearchArg
{
get { return _textToSearchArg; }
set
{
_textToSearchArg = value;
RaisePropertyChanged();
}
}
public string PatternArg
{
get { return _patternArg; }
set
{
_patternArg = value;
RaisePropertyChanged();
}
}
public KnuthMorrisPrattViewModel()
{
}
}
}
I know that Callback throws and exception but my goal here is to just see under the debugger that this callback is invoked. Then I add correct implementation.
EDIT:
I think I missed important note about my issue. When I update TextToSearchArg property from code everything works correctly. The only problem is that when I set some text in RichTextBox underlying property is not updated.
What am I missing? Thanks a lot in advance.
Nothing in your code shows that the Attached property is bound to the RichTextBox events, hence it won't ever be called if the content/text in RichTextBox changes.
You'd need to subscribe to the RichTextBox.TextChanged event.
public partial class KnuthMorrisPrattView : UserControl
{
public KnuthMorrisPrattView()
{
InitializeComponent();
this.TextBox.TextChanged += OnTextChanged;
}
...
public void OnTextChanged(object sender, TextChangedEventArgs e)
{
// Get the text from the event and set your Text Property
string text = ...;
SetText(this, text);
}
}
Edit:
In case you want to listen to another control's Dependency/Attached Property changes, use
DependencyPropertyDescriptor.FromProperty(ControlClassName.DesiredPropertyProperty, typeof(ControlClassName)).AddValueChanged(dependencyObject, OnDesiredPropertyChanged);
Where...
ControlClassName is the class containing the Dependency Property (i.e. RichTextBox in your case or the class where the Dependency/Attached Property is defined
'DesiredPropertyPropertyis the name of your property (i.e.TextProperty`
dependencyObject is the instance of object where the DesiredPropertyProperty is set on
OnDesiredPropertyChanged method to call when the property value changes
On a side note:
You should have Code-Behind in the view. There is no requirement that Dependency Properties or Attached Properties have to be defined inside the same class as the are meant for.
Code behind should only be used, if it's an reusable User Control but the naming of your class suggest it's not a User Control (even though it derives form User Control) but a View.
A view is application specific and can't be reused outside that specific app and is only meant to display a certain content. If you make a "LoginControl" then it it can be made to be reusable in other. A "LoginView" on other side doesn't suggest re-usability.
Maybe
Mode=TwoWay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged
on the binding missing?
I have a user control which contains a textbox and have created a get/set in the usercontrol to get/set the text property of the textbox.
public class OpenFileControl : UserControl
{
StackPanel sp;
public TextBox tb;
public string Text { get { return tb.Text; } set { tb.Text = value; } }
I then want to set this value based on a binding later on -
<gX3UserControls:OpenFileControl Text="{Binding Value}" />
But I get the following exception
A 'Binding' cannot be set on the 'Text' property of type 'OpenFileControl'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
After some investigation It seems Text needs to be a dependency property, but If I do that I cant work out how to pass the value on to the textbox.
How can I fix this.
Consider using something like this.
Control XAML:
<UserControl x:Class="WpfTestBench.OpenFileControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBox Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},
Path=Filename, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</UserControl>
Control codebehind:
using System.Windows;
namespace WpfTestBench
{
public partial class OpenFileControl
{
public static readonly DependencyProperty FilenameProperty =
DependencyProperty.Register("Filename", typeof (string), typeof (OpenFileControl));
public OpenFileControl()
{
InitializeComponent();
}
public string Filename
{
get { return (string)GetValue(FilenameProperty); }
set { SetValue(FilenameProperty, value); }
}
}
}
Main XAML:
<Window x:Class="WpfTestBench.OpenFileWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfTestBench="clr-namespace:WpfTestBench"
Title="OpenFileWindow" Width="300" SizeToContent="Height">
<StackPanel>
<wpfTestBench:OpenFileControl x:Name="In" Filename="{Binding SelectedFilename, UpdateSourceTrigger=PropertyChanged}" />
<wpfTestBench:OpenFileControl x:Name="Out" Filename="{Binding ElementName=In, Path=Filename}" />
</StackPanel>
</Window>
Main codebehind:
namespace WpfTestBench
{
public partial class OpenFileWindow
{
public OpenFileWindow()
{
InitializeComponent();
DataContext = this;
}
public string SelectedFilename { get; set; }
}
}
Execution result (after typing something in the first control):
If you define the dependency property as the static and the actual property, you can write whatever code behind you want in the body of the property.
public const string TextPropertyName = "Text";
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
TextPropertyName,
typeof(string),
typeof(MyControl),
new UIPropertyMetadata(false));
In the getter and setter you can do something like textBox1.Text = value; but you'd probably be better served using a binding to the property instead. MVVM frameworks make light work of this sort of thing quite often. You might find more success defining a ViewModel (a class with an appropriate FielPath variable for example) and setting the DataContext of the new UserControl to be an instance of the ViewModel class, using Bindings to do the heavy lifting for you.
I am creating a custom "PageHeaderControl" UserControl, with a header property:
public partial class PageHeaderControl: UserControl
{
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header",
typeof(string), typeof(PageHeaderControl),
new PropertyMetadata(""));
public string Header
{
get { return GetValue(HeaderProperty) as string; }
set { SetValue(HeaderProperty, value); }
}
}
In the XAML for that control, I have:
<sdk:Label Content="{Binding Header,Mode=TwoWay}" />
Now for the problem: When I create the control, binding it only works to do this:
<my:PageHeaderControl Header="This is my page header" />
And it does not work to do this, where PageHeader is the property in my ViewModel holding the header value:
<my:PageHeaderControl Header="{Binding PageHeader,Mode=TwoWay}" />
I thought maybe my properties were messed up, but this also works:
<TextBlock Text="{Binding PageHeader,Mode=TwoWay}" />
Any ideas as to what the problem could be!
Thanks so much!!!
Edit:
In my ViewModel, PageHeader is this:
private string _pageHeader = "This is my page header";
public string PageHeader
{
get
{
return _pageHeader;
}
set
{
_pageHeader = value;
RaisePropertyChanged("PageHeader");
}
}
Edit 2:
When I put a breakpoint inside the "get" for my PageHeader property, it does not get hit AT ALL, unless I add in the TextBlock...
If I understand you correctly you're trying to bind a property of an element within your control's XAML markup to the property of the control itself.
If this is the case, see if the following helps you.
PageHeaderControl.xaml:
<UserControl x:Class="TryElementBinding.PageHeaderControl"
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"
x:Name = "MyControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Text="{Binding Header, ElementName=MyControl}"></TextBlock>
</Grid>
PageHeaderControl.xaml.cs:
public partial class PageHeaderControl : UserControl
{
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(string), typeof(PageHeaderControl), new PropertyMetadata(""));
public string Header
{
get
{
return GetValue(HeaderProperty) as string;
}
set
{
SetValue(HeaderProperty, value);
}
}
public PageHeaderControl()
{
InitializeComponent();
}
}
ViewModel.cs:
public class ViewModel : INotifyPropertyChanged
{
private string _pageHeader = "This is my page header";
public string PageHeader
{
get
{
return _pageHeader;
}
set
{
_pageHeader = value;
PropertyChanged(this, new PropertyChangedEventArgs("PageHeader"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
MainPage.xaml:
<Grid x:Name="LayoutRoot" Background="White">
<my:PageHeaderControl Header="{Binding PageHeader, Mode=TwoWay}"></my:PageHeaderControl>
</Grid>
MainPage.xaml.cs:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
I'm a little bit confused and I think you missed the syntax of Binding inline expression.
after "{Binding" comes Path to your property. Is "PageHeader" is a path to your property?!
I think you mean this:
<my:PageHeader Header="{Binding PageHeader, Mode=TwoWay}" />
<TextBlock Text="{Binding PageHeader, Mode=TwoWay}" />
The problem is that Binding expression only works when you set the value of property using SetValue method and notify the parent DependencyObject that specific property has changed!
You should use a DependencyProperty to have TwoWay Binding on it, OR implement System.ComponentModel.INotifyPropertyChange interface in your class and notify the Binding object manually by calling PropertyChanged event in the interface.
The definition of PageHeader property should be like this:
public static readonly DependencyProperty PageHeaderProperty = DependencyProperty.Register("PageHeader", typeof(string), typeof(YOUROWNER), new PropertyMetadata(""));
public string PageHeader
{
get { return GetValue(PageHeaderProperty) as string; }
set { SetValue(PageHeaderProperty, value); }
}
Cheers