Passing SelectedItem of a TreeView UserControl to the calling Window - c#

I've created a dialog (Window) in which I'm using a UserControl with a TreeView. Now I need the SelectedItem of the TreeView (the Getter) in my dialog/window.
I have tried it with a DependencyProperty, but if I set a BreakPoint to the SelectedItem property of the dialog, it doesn't trigger.
My Window:
<itemViewer:ItemViewerControl DataContext="{Binding ListOfWorldItems}"
SelectedItem="{Binding SelectedWorldItem, Mode=TwoWay}"/>/>
with CodeBehind:
public WorldItem SelectedWorldItem
{
get { return selectedWorldItem; }
set
{
selectedWorldItem = value;
NotifyPropertyChanged("SelectedWorldItem");
}
}
My UserControl
<itemViewer:ItemViewerControl DataContext="{Binding ListOfWorldItems}" />
with CodeBehind:
<UserControl ... >
<UserControl.Resources>
...
</UserControl.Resources>
<Grid>
<TreeView x:Name="WorldItemsTreeView"
SelectedItemChanged="TreeView_SelectedItemChanged"
ItemsSource="{Binding}" />
</Grid>
</UserControl>
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(WorldItem), typeof(ItemViewerControl), new UIPropertyMetadata(null));
public ItemViewerControl()
{
InitializeComponent();
DataContext = this;
}
public WorldItem SelectedItem
{
get { return (WorldItem)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = (WorldItem)WorldItemsTreeView.SelectedItem;
}

The issue that you are running into is that you are setting the DataContext on your UserControl and then attempting to declare a binding. By default the binding will source its value from the DataContext of the element which contains the binding. In this case that is ListOfWorldItems, not the dialog. So the binding of the SelectedItem property on the UserControl actually fails (you can see this in the output window when debugging the application).
One way to resolve this is to explicitly set the source for the binding, instead of relying on the default behavior. If you change the line in your dialog to the following...
<itemViewer:ItemViewerControl DataContext="{Binding ListOfWorldItems}"
SelectedItem="{Binding SelectedWorldItem, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=Window}"/>
It should now look to your dialog as the source for the binding and correctly establish binding between your UserControl and the dialog. Be careful that any other bindings you establish between your UserControl and the dialog also explicitly establish the source, otherwise they will run into the same problem you encountered here.
It doesn't look like it is contributing to the issue but as an additional note you are setting the DataContext for your UserControl twice. Once in the constructor for the UserControl you are self-referencing and then it is overwritten when setting the DataContext in the dialog. In this case it doesn't look like it is causing a problem (aside from some minor inefficiency), but it could have unexpected side-effects if you ever changed how the DataContext for the UserControl is being set in the dialog.

Related

How to use a user control dependency property when setting its data context?

I've made a user control which contains a command, to be called in response to a certain event. This command is a dependency property. I want to use it in the main window like this:
<local:myUserControl Command="{Binding someCommand}"/>
The "myCommand" is the dependency property I created for this user control. And I bind it to a command of the view model of the main window ("someCommand").
The problem is that I am setting the datacontext of my usercontrol (I have a view model for it), and it seems to reset the "Command" to null… Here is the code-behind of my view model:
public partial class myUserControl : UserControl, ICommandSource
{
public myUserControl()
{
this.DataContext = new myViewModel();
InitializeComponent();
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(myUserControl), new PropertyMetadata(null));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(myUserControl), new PropertyMetadata(0));
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(myUserControl), new PropertyMetadata(null));
private void TextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
Command.Execute(this.CommandParameter);
}
}
The code of my user control could be the Following:
<UserControl x:Class="myApp.myUserControl"
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:myApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock MouseUp="TextBlock_MouseUp">
</TextBlock>
</Grid>
</UserControl>
(I know that this element seems a bit silly (or useless), but I have simplified it to test what didn't worked and also in order to ask a rather simple question).
I have discovered that, if I comment the "this.DataContext = new myViewModel();" line, the binding to the command works perfectly. And when I uncomment this line and put a breakpoint in the "TextBlock_MouseUp", the "Command" property is equal to null...
Would there be a way to resolve this problem? I have some complicated code in my view model (so I'm quite forced to keep this line "this.DataContext = new myViewModel();"), and I am not sure I could find another solution than having a "Command" dependency property in my user control…
To be sure I give a maximum of informations, I have the following code in the view model of my main window:
public ICommand someCommand { get; set; }
//Constructor
public MainWindowViewModel()
{
this.someCommand = new RelayCommand((obj) => { return true; },
(obj) =>
{
//I put a breakpoint here
int dummy = 0;
});
}
(The RelayCommand class is a standard RelayCommand class, with a "Predicate" CanExecute and an "Action Execute).
I hope this question is not a duplicate… I have found several similar question, but they did not seem to answer mine...
I'm really sorry for this question which was in fact a bit silly. I hadn't understand very well what happens during a binding. I thought that this code line in the MainWindow…
<local:myUserControl Command="{Binding someCommand}"/>
…would have made an attempt to bind the UserControl's "Command" property to the "someCommand" of the datacontext of the MainWindow. In fact, as #elgonzo pointed out, the binding looks up in the UserControl's datacontext for the "someCommand" property (and not in the MainWindow's datacontext!!). Therefore, setting the UserControl's datacontext with this line…
this.DataContext = new myViewModel();
...was preventing the binding to be correctly done (since it looks for the "someCommand" property of the UserControl's datacontext, which is now "myViewModel", which does not contain "someCommand"...).
To fix this, I had to change the binding like this:
<local:myUserControl Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},
Path=DataContext.someCommand}"/>
I've found this solution here: https://stackoverflow.com/a/1127964/11609068.
Maybe it is not the best way to do it (the "Path= DataContext. someCommand" make me think this, it doesn't seem very elegant), but it works. Another way to do it is to name the MainWindow (x:Name="someName"), so that the binding is a bit simpler:
<local:myUserControl Command="{Binding ElementName=someName, Path=DataContext.someCommand}"/>
Again, sorry and many thanks to #elgonzo.

XAML binding not working on dependency property?

I am trying (and failing) to do data binding on a dependency property in xaml. It works just fine when I use code behind, but not in xaml.
The user control is simply a TextBlock that bind to the dependency property:
<UserControl x:Class="WpfTest.MyControl" [...]>
<TextBlock Text="{Binding Test}" />
</UserControl>
And the dependency property is a simple string:
public static readonly DependencyProperty TestProperty
= DependencyProperty.Register("Test", typeof(string), typeof(MyControl), new PropertyMetadata("DEFAULT"));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
I have a regular property with the usual implementation of INotifyPropertyChanged in the main window.
private string _myText = "default";
public string MyText
{
get { return _myText; }
set { _myText = value; NotifyPropertyChanged(); }
}
So far so good. If I bind this property to a TextBlock on the main window everything works just fine. The text update properly if the MyText changes and all is well in the world.
<TextBlock Text="{Binding MyText}" />
However, if I do the same thing on my user control, nothing happens.
<local:MyControl x:Name="TheControl" Test="{Binding MyText}" />
And now the fun part is that if I do the very same binding in code behind it works!
TheControl.SetBinding(MyControl.TestProperty, new Binding
{
Source = DataContext,
Path = new PropertyPath("MyText"),
Mode = BindingMode.TwoWay
});
Why is it not working in xaml?
The dependency property declaration must look like this:
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register(
nameof(Test),
typeof(string),
typeof(MyControl),
new PropertyMetadata("DEFAULT"));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
The binding in the UserControl's XAML must set the control instance as the source object, e.g. by setting the Bindings's RelativeSource property:
<UserControl x:Class="WpfTest.MyControl" ...>
<TextBlock Text="{Binding Test,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</UserControl>
Also very important, never set the DataContext of a UserControl in its constructor. I'm sure there is something like
DataContext = this;
Remove it, as it effectively prevents inheriting a DataContext from the UserConrol's parent.
By setting Source = DataContext in the Binding in code behind you are explicitly setting a binding source, while in
<local:MyControl Test="{Binding MyText}" />
the binding source implicitly is the current DataContext. However, that DataContext has been set by the assignment in the UserControl's constructor to the UserControl itself, and is not the inherited DataContext (i.e. the view model instance) from the window.

WPF UserControl property binding

I'm trying to create a WPF UserControl which contains 2 buttons. I use this UserControl in a Window and apply a Window.Resource value to set the background of one button inside the user control.
Currently I have:
window.xaml
<Window.Resources>
<SolidColorBrush Color="Brown" x:Key="theBG"></SolidColorBrush>
</Window.Resources>
<theControl:TheControl
x:Name="TheControl"
buttonBG="{Binding Source={StaticResource theBG}}" />
usercontrol.xaml.cs
public SolidColorBrush buttonBG
{
get { return base.GetValue(buttonBGProperty) as SolidColorBrush; }
set { base.SetValue(buttonBGProperty, value); }
}
public static readonly DependencyProperty buttonBGProperty = DependencyProperty.Register("buttonBG", typeof(SolidColorBrush), typeof(DataPanel), null);
usercontrol.xaml
<Button ... Background="{Binding buttonBG}">
I was expecting this to work but the background is not the one I set in the window resource.
What am I doing wrong?
Background="{Binding buttonBG}"
Implies either that you changed the DataContext of the UserControl, which you should never do. Or that the binding is just wrong.
Use
Background="{Binding buttonBG, ElementName=control}"
Naming your UserControl root element control. RelativeSource works as well.
Try placing it in a separate model or even a viewmodel that has INotifyPropertyChanged. When you add view code in the cs for an xaml file, you need to bind with relativesource self and its hacky and goes against MVVM. I would create a seperate ViewModel with a Brush that has NotifyPropertyChanged baked into it. This will tell the UI to change everything its bound to on value change.
In the Window, bind your viewmodel to datacontext. In the viewmodel you can put:
private Brush _bgColor;
public Brush BgColor
{
get{return _bgColor;
}
set
{
_bgColor = value;
OnPropertyChanged("BgColor");
}
Create an ICommand, and bind your button to it like this in the viewmodel:
ICommand ChangeBgColor {get;set;
And in the XAML for the Button:
Command="{Binding Path=DataContext.ChangeBgColor,RelativeSource={RelativeSorce Mode=FindAncestor,AncestorType={x:Type UserControl}}"
This will fire the ICommand, bound to the viewmodel that is the datacontex of the window that you are working with.
And in that code for the ICommand change out your colors, you could do it like this:
private void OnChangeBgColor(object param){
var bc = new BrushConverter();
BgColor = (Brush)bc.ConvertFrom("#fff");
}
With the MVVM pattern, you want to get away from putting unnecessary code in the xaml.cs files and start putting them into viewmodels and models.

WPF :: Custom Control's Dependency Properties Aren't Working

I'm making a Ribbon control for a WYSIWYG HTML editor. The ribbon has the typical Bold, Italic, Underline, FontFamily, etc. controls that you'd expect to see. I'll focus on the Bold functionality for this example.
I want the Ribbon to be reuseable, so I've added a Dependency Property (DP) and associated property wrapper to the control's code behind (standard boilerplate stuff):
public partial class EditorRibbon: UserControl
{
public static readonly DependencyProperty IsBoldProperty =
DependencyProperty.Register(
"IsBold",
typeof (bool),
typeof (EditorRibbon),
new PropertyMetadata(default(bool)));
public bool IsBold
{
get { return (bool) GetValue(IsBoldProperty); }
set { SetValue(IsBoldProperty, value); }
}
}
... and in the XAML I have my RibbonToggleButton, and I've bound the IsChecked property to the dependency property:
<UserControl x:Class="My.EditorRibbon">
<r:RibbonToggleButton Command="ToggleBold"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"
SmallImageSource="{StaticResource ToggleBoldIcon}"
IsChecked="{Binding IsBold}" />
</UserControl>
In my Editor window, I've bound the IsBold property of the EditorRibbon to a conventional property on the window's ViewModel:
<Window x:class="My.MainWindow>
<My.EditorRibbon IsBold="{Binding SelectionIsBold}"/>
</Window>
Here is the SelectionIsBold property:
public bool SelectionIsBold
{
get { return _selection.IsBold(); }
}
... and I raise the NotifyPropertyChanged() event (in the MainWindow's ViewModel) whenever the selection in the RichTextBox changes:
public class MainWindowViewModel : BaseViewModel
{
public MainWindowViewModel(MainWindow window)
{
rtb.SelectionChanged += rtb_OnSelectionChanged;
}
private void rtb_OnSelectionChanged(object sender, RoutedEventArgs routedEventArgs)
{
NotifyPropertyChanged(()=>SelectionIsBold);
}
}
To my mind, this should be enough to change the IsChecked state of the RibbonToggleButton whenever the selection changes... but it doesn't. Despite changing the selection, and despite the NotifyPropertyChanged() firing as expected, a breakpoint on the SelectionIsBold property (yes, I've deselected VS's "Step Over Property" setting) is never hit. Somewhere, the request to refresh the value isn't propagating correctly.
Do I need to trigger NotifyPropertyChanged() on the IsBold property after the value is set in the setter?
Change the IsBold binding to the following
<UserControl x:Class="My.EditorRibbon" x:Name="EditorRibbonInstance">
<r:RibbonToggleButton Command="ToggleBold"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"
SmallImageSource="{StaticResource ToggleBoldIcon}"
IsChecked="{Binding IsBold, ElementName=EditorRibbonInstance, Mode=TwoWay}" />
</UserControl>
With that you are sure that the binding is going to the property of the control and not to the datacontext of the control
You have to fire notifypropertychanged in ViewModel. Try somethings like this in ViewModel:
protected void FirePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
->> FirePropertyChanged("SelectionIsBold")
The reason is: now, your data context is ViewModel, all of binding to ViewModel must be triggered by ViewModel's properties
first of all, I never saw the injection of the Window to the ViewModel before... are you using some Kind of DI for the injection?
I think it is not a good idea to use the selection changed Event on viewmodel... This is not mvvm from my Point of view...
Are you updating the _selection somewhere? Might be that you always checking the same selection?!
You are not properly binding the command property of your button.
Should reflect something like this:
Command="{Binding ToggleBold}"

How to set a Dependency Property Value?

Please reference code below for context.
On start up, the Text of the 2 TextBoxes will be "This is the Original Value".
When the TestBox's button ("Test Button") is clicked:
the text of the TestBox's TextBox will change to "Set By Test Button"
the other TextBox's value will NOT change.
When the Window's button is clicked, the text of BOTH TextBoxes should change to "Set By Window". However, only the plain TextBox gets updated, the TestBox does not. <-- THIS IS THE BUG!
It seems that the way i'm (re)setting the Test property from within the TestBox obliterates the binding.
What is the proper way of changing a Dependency Property from within the user control itself without breaking bindings?
Example code:
I've got a UserControl, TestBox that looks like this:
TestBox.xaml:
<UserControl x:Class="Company.UserControls.TestBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="TextBoxControl">
<StackPanel>
<TextBox MinWidth="100" Name="TestTextBox"
Text="{Binding Path=Test, ElementName=TextBoxControl, Mode=TwoWay}"
/>
<Button MinWidth="100" Content="Test Button"
Click="ButtonBase_OnClick" />
</StackPanel>
</UserControl>
TestBox.xaml.cs:
using System.Windows;
namespace Company.UserControls
{
public partial class TestBox
{
public const string TestString = "Set By Test Button";
public TestBox()
{
InitializeComponent();
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register(
"Test",
typeof(string), typeof(TestBox),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
/****** THIS OBLITERATES THE BINDING ******/
Test = TestString;
/****** THIS OBLITERATES THE BINDING ******/
}
}
}
And a Window that uses the control like this:
MainWindow.xaml:
<Window x:Class="Company.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:Company.UserControls"
Title="MainWindow">
<StackPanel x:Name="MyStackPanel">
<TextBox Text="{Binding Path=MyTestValue, Mode=OneWay}"/>
<u:TestBox x:Name="MyTestBox"
Test="{Binding Path=MyTestValue, Mode=OneWay}"/>
<Button Content="Click" Click="ButtonBase_OnClick" />
</StackPanel>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace Company
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
MyStackPanel.DataContext = new MyThing
{
MyTestValue = "This is the Original Value"
};
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MyStackPanel.DataContext = new MyThing
{
MyTestValue = "Set by Window"
};
}
}
public class MyThing
{
public string MyTestValue { get; set; }
}
}
The problem is that you are asking the binding system to get out of sync. The whole system is designed to keep all bound elements in sync. The only cases under which you can set a value on a dependency property without destroying the underlying binding are when the binding mode is set to "TwoWay" or "OneWayToSource". Under these conditions the value is transferred back to the source and consequently, the system is kept in sync. However, in your case a two way binding will cause both buttons to change both textboxes.
You will need to use two dependency properties TestBox. The first dependency property will be bound to the internal text box, and the second will be bound to in the parent window. Then you will need to add a property change handler to the second dependency property (which is done in the FrameworkPropertyMetadata). In this handler, simply set the value on the first dependency property.
Since you are using a UserControl with a code behind anyways, a simpler solution is to only have the second dependency property mentioned above and to directly set the value (from you event handler and the property change handler) onto the textbox via its x:Name.
Let me know if you need any more clarification.

Categories