How to achieve databinding with a user control in WPF? - c#

I'm fairly new to WPF and I have some problems getting databinding to work as I want. I've written a user control which contains a TextBox whose Text-Property I want to bind to a property of my UserControl, which I want to bind again to something else.
What am I missing?
XAML
<!-- User Control -->
<TextBox Text="{Binding Path=TheText}" />
<!-- Window -->
<WpfApplication1:SomeControl TheText="{Binding Path=MyStringProp}" />
C#
// User Control ----
public partial class SomeControl : UserControl
{
public DependencyProperty TheTextProperty = DependencyProperty
.Register("TheText", typeof (string), typeof (SomeControl));
public string TheText
{
get
{
return (string)GetValue(TheTextProperty);
}
set
{
SetValue(TheTextProperty, value);
}
}
public SomeControl()
{
InitializeComponent();
DataContext = this;
}
}
// Window ----
public partial class Window1 : Window
{
private readonly MyClass _myClass;
public Window1()
{
InitializeComponent();
_myClass = new MyClass();
_myClass.MyStringProp = "Hallo Welt";
DataContext = _myClass;
}
}
public class MyClass// : DependencyObject
{
// public static DependencyProperty MyStringPropProperty = DependencyProperty
// .Register("MyStringProp", typeof (string), typeof (MyClass));
public string MyStringProp { get; set; }
// {
// get { return (string)GetValue(MyStringPropProperty); }
// set { SetValue(MyStringPropProperty, value); }
// }
}
Best RegardsOliver Hanappi
PS: I've tried to implement the INotifyPropertyChanged interface on my user control, but it did not help.

You want to bind the Text property of your TextBox back to the TheText property of the UserControl it lives in, right? So you need to tell the binding where the property lives. There's a couple of ways to do this (you can do it with a RelativeSource using FindAncestor) but the easiest way is to give the UserControl a "name" in the XAML and bind using element binding:
<UserControl ...
x:Name="me" />
<TextBox Text="{Binding TheText,ElementName=me}" />
</UserControl>
Now your TextBox will reflect the value you've assigned (or bound) to your "SomeControl.TheText" property - you needn't change any of your other code, although you'll probably want to implement INotifyPropertyChanged on your underlying MyClass object so that the binding knows when the property has changed.

Matt has provided a solution to your problem. Here is a little more explanation and a hint to stop this problem in future.
As SomeControl.DataContext is set in the SomeControl constructor, the window's binding TheText="{Binding Path=MyStringProp}" has a Source of type SomeControl, not MyClass as you intended.
Any bindings that fail at runtime cause debug messages to be logged to the output panel of Visual Studio. In this case, you would have seen that no such property 'MyStringProp' exists on object of type 'SomeControl', which should have raised your suspicions.
I think everyone finds WPF data binding takes some time to learn and especially to debug, but persevere. Data binding in WPF is really fantastic, and I still get a kick out of knowing how easily it makes the data on my UIs stay up to date.

Related

Access Indirect property from XAML - WPF

I have a Control class named MyControl and it has direct bool property AccessDirectProperty and an Object MyControlSettings
public class MyControl : Control
{
public bool AccessDirectProperty
{
get; set;
}
public MyControlSettings ControlSettings
{
get;set;
}
}
Please find the MyControlSettings class details
public class MyControlSettings
{
public bool AccessIndirectProperty
{
get;set;
}
}
Direct property AccessDirectProperty can be accessible from XAML without any error.
<Window>
<Grid>
<local:MyControl AccessDirectProperty="True"/>
</Grid>
</Window>
But I cannot access the property AccessIndirectProperty from the object ControlSettings in XAML. The below code fails to do that.
<Window>
<Grid>
<local:MyControl AccessDirectProperty="True" ControlSettings.AccessIndirectProperty=""/>
</Grid>
</Window>
Can anyone help me on this?
I'm afraid that XAML does not support accessing "nested" properties.
You could, however, make ControlSettings an independent class with attached properties:
public class ControlSettings : DependencyObject
{
public static readonly DependencyProperty AccessIndirectPropertyProperty =
DependencyProperty.RegisterAttached(
"AccessIndirectProperty", typeof(bool), typeof(ControlSettings),
new PropertyMetadata(false));
public static bool GetAccessIndirectProperty(DependencyObject d)
{
return (bool) d.GetValue(AccessIndirectPropertyProperty);
}
public static void SetAccessIndirectProperty(DependencyObject d, bool value)
{
d.SetValue(AccessIndirectPropertyProperty, value);
}
}
Then,
<local:MyControl x:Name="myControl"
AccessDirectProperty="True"
ControlSettings.AccessIndirectProperty="True" />
would set a value which could be accessed via
var p = ControlSettings.GetAccessIndirectProperty(myControl); // yields True
Now, on a technical level, the following is not useful to modify a property of an existing MyControlSettings instance provided through MyControl.ControlSettings. However, if your use case allows creating and assigning an entirely new MyControlSettings instance to MyControl.ControlSettings, you can do so in XAML:
<local:MyControl>
<ControlSettings>
<local:MyControlSettings AccessIndirectProperty="true" />
</ControlSettings>
</local:MyControl>
A side note: The term "ControlSettings" suggest to me that you want to kind of "package" control settings/properties in some kind of MyControlSettings "container". Now, i don't know why and what the motivation for this is, but keep in mind that choosing this approach can make it very hard or even impossible to use data bindings in a meaningful way where such a settings property is supposed to be the binding target. If you want to be able to use individual settings as binding target (like AccessIndirectProperty="{Binding Path=Source}"), i would rather suggest your MyControl exposes those settings individually as DependencyProperties.

Data-binding ObservableCollection<T> with ComboBox doesn't work with Dependency Property

I have a list of objects (ObservableCollection subjectlist) and want to display them in a Combobox via data-binding and dependency property.
WPF Data Binding to a Combo Box
I searched on stackoverflow and tried to implement the solution of Craig Suchanec in the link above. (tried the whole day now and I just don't get what's wrong with my code)
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public static readonly DependencyProperty SubjectListProperty =
DependencyProperty.Register("SubjectList",
typeof(ObservableCollection<Subject>),
typeof(MainWindow));
private ObservableCollection<Subject> subjectList = new ObservableCollection<Subject>();
Initialization init1;
public ObservableCollection<Subject> SubjectList
{
get { return (ObservableCollection<Subject>)GetValue(SubjectListProperty); }
// get { return subjectList; }
}
public MainWindow()
{
init1 = new Initialization();
subjectList = init1.createMenuSubject();
InitializeComponent();
//this.comboBox.DataContext = SubjectList;
}
}
MainWindow.xaml
<Grid>
<ComboBox x:Name="comboBox" HorizontalAlignment="Left"VerticalAlignment="Top" Width="120" Margin="321,10,0,0"
ItemsSource="{Binding ElementName=mainWindow, Path=SubjectList}" DisplayMemberPath="Name"/>
</Grid>
It DOES work if I just set the DataContext and work without dependency property, but as soon as I try to use the dependency property for data-binding it does NOT and I don't see the significant difference between my implementation and the solution given in the link.
It would be much appreciated, if somebody could help me with this problem.
I can't see anywhere in your code where you are actually setting the value of the SubjectList property.
You are however setting the value of subjectList, but you're binding to SubjectList. Note the casing difference.
You should write:
public ObservableCollection<Subject> SubjectList
{
set { base.SetValue(SubjectListProperty, value); }
get { return (ObservableCollection<Subject>)base.GetValue(SubjectListProperty); }
}
instead of
public ObservableCollection<Subject> SubjectList
{
set { base.SetValue(SubjectListProperty, value); }
get { return subjectList; }
}
or any other ad hoc format. You are setting subjectList in your constructor MainWindow(), however, it will not set the value of SubjectList (with Capital S) and a property change event is never raised. Remove subjectList.
If you are wondering why the DataContext approach works, you should note it will work even if you do not use a DepenedencyProperty. However, if you implement INotifyPropertyChange, it will work with setting ElementName too.

How to access a custom control dependency propery from its viewmodel

I'm working on a multiple document viewer (a simple window with a custom control, each with a separate viewmodel). When clicking on a filename, a new instance of the user control is added to the main window. The user control has a dependency property which holds the path to the filename, defined in it's code-behind. Now i'm struck on how to get the value of this property from the user control to the viewmodel, so it can show the actual document. Any Hints?
<ctrl:DocViewerControl x:Key="docviewer" DocumentSource="{Binding SelectedItem.Path, ElementName=docList}"/>
I use this code in main window to make new instances of my user control where DocumentSource is the dependency property i need access to, as stated above.
Edit:
Following is the (relevant) code for the view and the viewmodel of my control, specific to the dependancy property value capture problem i have.
UserControl.xaml.cs
public partial class ToolboxControl : UserControl
{
public static readonly DependencyProperty DocumentSourceProperty = DependencyProperty.Register("DocumentSource",
typeof(string), typeof(ToolboxControl), new UIPropertyMetadata(new PropertyChangedCallback(OnDocumentSourceChanged)));
public ToolboxControl()
{
InitializeComponent();
}
public string DocumentSource
{
get { return (string)GetValue(DocumentSourceProperty); }
set { SetValue(DocumentSourceProperty, value); }
}
private static void OnDocumentSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
}
}
PV_ViewModel.cs
public class PV_ViewModel : ObservableObject
{
.....
public string DocumentSource
{
get { return (String.IsNullOrEmpty(_documentsource)? (_documentsource = #"about:blank") : _documentsource); }
set { SetField<string>(ref _documentsource, value, "DocumentSource"); }
}
.....
public PV_ViewModel()
{
PropertyChanged += DocumentSourceChanged;
}
.....
protected void DocumentSourceChanged(object sender, PropertyChangedEventArgs e)
{
if (sender != null)
{
switch(e.PropertyName)
{
case "DocumentSource":
{
// show the document and whatsoever
break;
}
}
}
}
.....
}
Neither the getter nor the setter of the viewmodel DocumentSource property get accessed from anywhere, despite the UserControl in MainWindow had is DocumentSourceProperty filled in with the current document path string. (i can see it form a collection of currently opened document on the main app).
To clarify: the application solution contains MainWindow project (the main view, a simple window with a TreeView and the UserControl container), the UserControl project (the (hopefully) standalone application used to show a single document when providing the path to the doc to show through the DocumentSource property.
I am not really sure I understand your problem (or if you understand how Dependency Properties work), so you may have to post a bit more of your code behind (with the DI for example)
Typically your DocViewerControl looks like this
public abstract class DocViewerControl : UserControl
{
public string Path
{
get { return (string)GetValue(PathProperty); }
set { SetValue(PathProperty, value); }
}
public static readonly DependencyProperty PathProperty =
DependencyProperty.Register("Path", typeof(string), typeof(DocViewerControl), new PropertyMetadata(string.Empty));
}
This will expose a Property in XAML of the control.
It's important here that you make it TwoWay binding, so any change from the UserControll will update the bounded field in your ViewModel.
Your ViewModel:
public class Doc1ViewModel : ViewModelBase {
private string path;
public string Path
{
get { return path;}
set {
if(path!=value) {
path = value;
OnPropertyChanged("Path");
}
}
}
}
Now, each time when you assign the property in your UserControl, the value in the ViewModel will be updated. As you can see, the Dependency Property consists from two properties. One static Dependency Property called PathProperty and one instance property called Path.
But having a closer look at it, it's not a real instance property at all. It just wraps calls around the Dependency Property by using GetValue and SetValue (which are derived from DependencyObject class, which every UI control inherits).
Hope this clears it up how Dependency Properties work, as it hard to tell what's wrong with your approach without seeing the code used.
In a nutshell, Dependency Properties (together with Attached Properties) extend the XAML code with TwoWay bindable properties (normal instance property can only be bound in one direction).

"Bindable Method" with OneWayToSource mode

I need to write a custom control that looks like a TextBox and that contains a method called Refresh() which main purpose will be to clear the Text and to roll back few other values.
The method shall become bindable somehow so that others can bind a property from their ViewModel with it. Hence why I am thinking that inside my custom control I will need an dependency property of type Action.
So far so logical but next problem is the method/dp may no get overriden on control side once users sets a two way binding on it. Basically I always have to deliver the method wrapper as Action to ViewModel and inside ViewModel other users may call it.
How do I do all this? It seems to me that I have to somehow get the binding of the method work like OneWayToSource.
I apologize in case its a duplicate. Futhermore thanks in advance guys.
EDIT: Please no alternative solutions. Those are the requirements and I have to stick to them.
I think that the simplest thing you can do here is to expose a bool property, maybe called IsCleared, and just call your method from that property when it becomes true. Exposing ICommand and/or delegate objects transfers the functionality out of your control, so you can't use those.
#ninjahedgehog, why can't you use a bool 'switch' property? Your requirement says 'so that others can bind a property from their ViewModel with it'... they can bind to a bool property from their view model. In my opinion, it seems to be your only option. As I said earlier, you can't use ICommand and/or delegate objects as that would transfer the functionality out of your control - that would enable other developers to write their own functionality rather than to just call yours.
What you really want is a method on your control that they could call from their view model... but view models shouldn't have any knowledge about the view controls, so you can't do that. The next best thing to that is creating a method that is called when a property is given a certain value. Here you have a few choices.
If you really don't like the bool switch idea, then how about an enum property? Create an enum with specific values like ClearText and whatever other functionality you would like to expose. Then the other developers simply set this property to the relevant instance to instantiate that functionality... I only suggested the bool switch property because it seems as if you only want to expose one piece of functionality.
One last point to note about using the bool switch property... as it is a switch, you need to reset it after use, or just never actually set it:
public bool IsTextClear
{
get { if (value) ClearText(); }
}
I dont know why you need this coz the person who is using your control can directly call the method from the code behind. But if you want that there should be some property like ClearMe on control and when set to true it should clear the control then you can define the dependency property and listen to its change in control like below and call Refresh from there.
public static readonly DependencyProperty ClearMeProperty = DependencyProperty.Register
(
"ClearMe",
typeof(bool),
typeof(MyControl),
new FrameworkPropertyMetadata(false, OnClearMeChanged)
);
public bool ClearMe
{
get { return (bool)GetValue(ClearMeProperty); }
set { SetValue(ClearMeProperty, value); }
}
private static void OnClearMeChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as MyControl;
if((bool)e.NewValue)
{
control.Refresh()
}
}
and you can bind this property to your ViewModel property. whenever ViewModel property will change to true. Property Change will be fired in control and will refersh it.
I editted my answer, as I wasn't understanding what you wanted. The only way I could come up with to do what you want was to use an Action DependencyProperty on the CustomControl and bind that to the ViewModel using a OneWayToSource binding, that way the Action from the control gets sent to the viewmodel. Within your customcontrol, you can test to make sure that only OneWayToSource binding is used and do something if not.. in this case, I add some text and made the background red.
View
<UserControl x:Class="WpfApplication1.Views.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
xmlns:controls="clr-namespace:WpfApplication1.Controls">
<UserControl.Resources>
<vm:TestViewModel x:Key="TestViewModel" />
</UserControl.Resources>
<StackPanel DataContext="{StaticResource TestViewModel}">
<StackPanel Orientation="Horizontal" Height="30">
<controls:CustomTextBox Width="300" Refresh="{Binding RefreshAction, Mode=OneWayToSource}" />
<Button Content="Refresh" Width="80" Command="{Binding RefreshFromView}" />
</StackPanel>
</StackPanel>
ViewModel
using System;
using System.ComponentModel;
namespace WpfApplication1.ViewModels
{
public class TestViewModel : INotifyPropertyChanged
{
public TestViewModel()
{
RefreshFromView = new RelayCommand(ExecuteRefreshFromView);
}
public Action RefreshAction { get; set; }
public RelayCommand RefreshFromView { get; set; }
private void ExecuteRefreshFromView(object parameter)
{
if (RefreshAction != null)
RefreshAction();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyOfPropertyChange(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Custom Control
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace WpfApplication1.Controls
{
public class CustomTextBox : TextBox
{
public CustomTextBox()
{
this.Loaded += CustomTextBox_Loaded;
}
void CustomTextBox_Loaded(object sender, RoutedEventArgs e)
{
BindingExpression bindingExpression = GetBindingExpression(RefreshProperty);
BindingMode mode = bindingExpression.ParentBinding.Mode;
if (mode != BindingMode.OneWayToSource)
{
Text = "Use OneWayToSource Binding only!";
Background = new SolidColorBrush(Colors.Red);
}
Refresh = new Action(DoRefresh);
}
private void DoRefresh()
{
Text = null;
}
public Action Refresh
{
get { return (Action)GetValue(RefreshProperty); }
set { SetValue(RefreshProperty, value); }
}
public static readonly DependencyProperty RefreshProperty = DependencyProperty.Register("Refresh", typeof(Action), typeof(CustomTextBox));
}
}
You could use a Command:
public class Command : ICommand
{
public void Execute(object parameter)
{
// Do whatever you have to do
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
In your ViewModel:
public ICommand Command { get; set; }
In your XAML (assuming that your Custom Control is composed of a TextBox and a Button for example):
<Button Click="{Binding Command}" />

Specify which Property goes between the opening and closing tag in Xaml

Consider the following Xaml
<Grid>
<TextBox>Text</TextBox>
<Button>Content</Button>
</Grid>
It will set the
Text Property of a TextBox (only WPF)
Content Property of a Button
Children Property of a Grid
But how is this specified? How do you specify which Property that goes between the opening and closing tag in Xaml?
Is this set by some metadata in the Dependency Property or what?
Thanks
There is a ContentPropertyAttribute that is applied to a class. WPF/Silverlight will use reflection to determine which property to use.
If you want to do this with a custom class, you can do it like so:
[ContentProperty("Bar")]
public class Foo : Control
{
public static DependencyProperty BarProperty = DependencyProperty.Register(
"Bar",
typeof(int),
typeof(Foo),
new FrameworkPropertyMetaData(0));
public int Bar
{
get { return (int)GetValue(BarProperty); }
set { SetValue(BarProperty, value); }
}
}
Then you could specify it in XAML like so:
<lcl:Foo>12</lcl:Foo>
Update
Since it is using reflection, you don't really need to do a DependencyProperty. For instance, this will also work:
[ContentProperty("Bar")]
public class Foo : Control
{
public int Bar { get; set; }
}

Categories

Resources