Dependency property propagation failing - c#

I made a UserControl called ObjectExplorer :
public partial class ObjectExplorer : UserControl
{
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(List<Node>), typeof(ObjectExplorer), new PropertyMetadata(new List<Node>(), ItemsSet));
public List<Node> Items
{
get { return (List<Node>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
}
Inside the XAML of the control it has :
<TreeView Name="treeView" ItemsSource="{Binding Items}" Padding="0,5,0,0" Grid.Row="1">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<StackPanel Orientation="Horizontal" Margin="4,3" ContextMenu="{Binding Converter={StaticResource NodeTypeToContextMenuConverter}}">
<Image Source="{Binding Converter={StaticResource StringToImageConverter}}" />
<TextBlock Text="{Binding Title}" Padding="4,0,0,0" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
So we can see, the TreeView is bound to the Items DP. Now I am using it in my MainWindow.xaml like so :
<fe:ObjectExplorer x:Name="objExplorer" Items="{Binding DatabaseObjects}" />
So this "instance" of the control, its Items is bound to the DP on MainWindow :
public partial class MainWindow : Window
{
public static readonly DependencyProperty DatabaseObjectsProperty = DependencyProperty.Register("DatabaseObjects", typeof(List<Node>), typeof(MainWindow), new PropertyMetadata(new List<Node>(), DatabaseObjectsPropertySet));
public List<Node> DatabaseObjects
{
get { return (List<Node>)GetValue(DatabaseObjectsProperty); }
set { SetValue(DatabaseObjectsProperty, value); }
}
}
Inside a method on MainWindow.cs I am doing :
List<Node> nodes = new List<Node>();
nodes.add(....);
DatabaseObjects = nodes;
What happens :
The DP on MainWindow triggers its set callback fine.
What does not happen :
The DP on ObjectExplorer does not get called, and nothing is displayed in the TreeView. I got it to work by naming the instance of the custom control (objExplorer) in mainwindow, then manually setting its Items whenever the callback for DatabaseObjects triggers...
private static void DatabaseObjectsPropertySet(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MainWindow)d).objExplorer.Items = (List<Node>)e.NewValue;
}
I would like to know... why?
EDIT:
The MainWindow's DataContext is set in XAML DataContext="{Binding RelativeSource={RelativeSource Self}}
I expect :
Code in MainWindow -> MainWindow.DatabaseObjects DP ||breaks|| ---(binding)--> objExplorer.Items ---> UC.Items ---(Internal Binding)--> TreeView.ItemSource

Related

WinUI 3 How to Bind Command to ViewModel property when using a DataTemplate?

I am using a Grid called MainGrid to position an ItemsRepeater whose ItemsSource is bound to an ObservableCollection within my ViewModel.
<muxc:ItemsRepeater
ItemsSource="{Binding Path=Molts}"
Layout="{StaticResource VerticalStackLayout}"
ItemTemplate="{StaticResource MoltTemplate}">
</muxc:ItemsRepeater>
I have created a DataTemplate
<DataTemplate x:Key="MoltTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="text" Text="{Binding ID}"></TextBlock>
<Button Command="{Binding DisplayAvailAIsCommand}" CommandParameter="{Binding ElementName=text, Path=Text}">Add</Button>
</StackPanel>
</DataTemplate>
which has a TextBox and Button. I want the Button to fire a command in my ViewModel but items within the ItemsRepeater have their DataContext set to their Model class and not the ViewModel. I found this post which states that I can change the Command of my Button to set the DataContext to my ViewModel by setting ElementName to a UI element that has as its DataContext the ViewModel
<Button Command="{Binding DataContext.DisplayAvailAIsCommand, ElementName=MainGrid}" CommandParameter="{Binding ElementName=text, Path=Text}">Add</Button>
The constructor of my window sets the DataContext of the MainGrid like this
public MainWindow()
{
this.InitializeComponent();
MainGrid.DataContext = new MoltViewModel();
}
However, this does not work and the command does not fire. What am I doing wrong?
You could create an attached property that sets the DataContext of the Button to a parent element of a specific type such as for example ItemsRepeater:
public static class AncestorSource
{
public static readonly DependencyProperty AncestorTypeProperty =
DependencyProperty.RegisterAttached(
"AncestorType",
typeof(Type),
typeof(AncestorSource),
new PropertyMetadata(default(Type), OnAncestorTypeChanged)
);
public static void SetAncestorType(FrameworkElement element, Type value) =>
element.SetValue(AncestorTypeProperty, value);
public static Type GetAncestorType(FrameworkElement element) =>
(Type)element.GetValue(AncestorTypeProperty);
private static void OnAncestorTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement target = (FrameworkElement)d;
if (target.IsLoaded)
SetDataContext(target);
else
target.Loaded += OnTargetLoaded;
}
private static void OnTargetLoaded(object sender, RoutedEventArgs e)
{
FrameworkElement target = (FrameworkElement)sender;
target.Loaded -= OnTargetLoaded;
SetDataContext(target);
}
private static void SetDataContext(FrameworkElement target)
{
Type ancestorType = GetAncestorType(target);
if (ancestorType != null)
target.DataContext = FindParent(target, ancestorType);
}
private static object FindParent(DependencyObject dependencyObject, Type ancestorType)
{
DependencyObject parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null)
return null;
if (ancestorType.IsAssignableFrom(parent.GetType()))
return parent;
return FindParent(parent, ancestorType);
}
}
Usage:
<DataTemplate x:Key="MoltTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="text" Text="{Binding ID}"></TextBlock>
<Button local:AncestorSource.AncestorType="muxc:ItemsRepeater"
Command="{Binding DataContext.DisplayAvailAIsCommand}"
CommandParameter="{Binding ElementName=text, Path=Text}">Add</Button>
</StackPanel>
</DataTemplate>
Please refer to this blog post for more information.

DependencyProperty for collection of buttons

I want to give the user the opportunity to set a collection of buttons in my CustomControl.
I tried to solve this with ItemsControl like this:
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type cc:MyCustomControl}}, Path=Buttons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Command}">
<Image Source="{Binding MyImageSource}"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Buttons DependencyProperty:
public static readonly DependencyProperty ButtonsProperty = DependencyProperty.Register(
"Buttons", typeof(IList), typeof(TileGrid), new PropertyMetadata(default(IList)));
public IList Buttons
{
get { return (IList) GetValue(ButtonsProperty); }
set { SetValue(ButtonsProperty, value); }
}
MyButton class:
public class MyButton: Button
{
public ImageSource MyImageSource { get; set; }
}
And how I want to see it in CustomControl implementation:
<cc:MyCustomControl>
<cc:MyCustomControl.Buttons>
<cc:MyButton Command="{Binding SignDocumentsCommand}"
MyImageSource="pack://application:,,,/CommonResources;component/Images/Icons/pen.png"/>
<cc:MyButton Command="{Binding DeleteDocumentsCommand}"
MyImageSource="pack://application:,,,/CommonResources;component/Images/Icons/remove.png"/>
</cc:MyCustomControl.Buttons>
</cc:MyCustomControl>
But it's not working. In live visual tree i see only MyButtons inside ItemsControl. Is this a right approach? Or i need to solve it another way?
The type that you are using for the Button items in the ItemsControl should not be derived from Button. You may use something simple like shown below. It is also not necessary to declare the Buttons property as dependency property. A simple ObservableCollection is sufficient.
public class MyButtonItem : DependencyObject
{
public static DependencyProperty CommandProperty = DependencyProperty.Register(
nameof(Command), typeof(ICommand), typeof(MyButtonItem));
public static DependencyProperty ImageSourceProperty = DependencyProperty.Register(
nameof(ImageSource), typeof(ImageSource), typeof(MyButtonItem));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public ImageSource ImageSource
{
get { return (ImageSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
}
public partial class MyCustomControl : UserControl
{
public MyCustomControl()
{
InitializeComponent();
}
public ObservableCollection<MyButtonItem> Buttons { get; }
= new ObservableCollection<MyButtonItem>();
}
The controls XAML would just be what you already have:
<ItemsControl ItemsSource="{Binding Buttons,
RelativeSource={RelativeSource AncestorType=UserControl}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Command}">
<Image Source="{Binding ImageSource}"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You would then add MyButtonItem objects to the Buttons collection:
<cc:MyCustomControl>
<cc:MyCustomControl.Buttons>
<cc:MyButtonItem
Command="{Binding SignDocumentsCommand}"
ImageSource="/CommonResources;component/Images/Icons/pen.png"/>
<cc:MyButtonItem
Command="{Binding DeleteDocumentsCommand}"
ImageSource="/CommonResources;component/Images/Icons/remove.png"/>
</cc:MyCustomControl.Buttons>
</cc:MyCustomControl>

How to execute command on parent window by User control button?

I want to make reusable jog user control. I added usercontrol on the main window.
I want to make like this
usercontrol button clicked -> 'UpJogClickCommand' call 'UpJogRelayCommand' -> execute method(UpJogMove)
But my code is Not working.. when i click button, main code do not execute 'UpJogMove'
[JogButtonControl.xaml]
<UserControl>
<Grid>
<Button Command="{Binding UpJogClickCommand}">
</Grid>
</UserControl>
[JogButtonControl.xaml.cs]
public partial class JogButtonControl : UserControl
{
public static readonly DependencyProperty UpJogClickCommandProperty =
DependencyProperty.Register(
"UpJogClickCommand",
typeof(ICommand),
typeof(JogButtonControl));
public ICommand UpJogClickCommand
{
get { return (ICommand)GetValue(UpJogClickCommandProperty); }
set { SetValue(UpJogClickCommandProperty, value); }
}
public JogButtonControl()
{
InitializeComponent();
}
}
[MainWindow.xaml]
<StackPanel x:Name="TempSP" Grid.Column="7">
<JogControl:JogButtonControl UpJogClickCommand="{Binding Path=UpJogRelayCommand}"
</StackPanel>
[MainWindow.xaml.cs]
private RelayCommand<object> _upJogRelayCommand;
public ICommand UpJogRelayCommand
{
get
{
if (_upJogRelayCommand == null)
{
_upJogRelayCommand = new RelayCommand<object>(UpJogMove);
}
return _upJogRelayCommand;
}
}
private void UpJogMove(object notUsed)
{
Debug.Print("UpJogExcuted():");
MoveToUpDirection();
}
You should specify the source object of the Binding in the XAML of your UserControl, e.g. by setting the RelativeSource property:
<UserControl>
<Grid>
<Button Command="{Binding UpJogClickCommand,
RelativeSource={RelativeSource AncestorType=UserControl}}">
</Grid>
</UserControl>

Nested Binding With WPF

I am newbie to WPF and curious to know how to nested bind the properties in WPF. I have made a sample app for better understanding. Please find below the following scenario's.
Working Code:
UserControl1 >> MainWindow
Created a UserControl and included it in the mainWindow.
UserControl1.xaml
<StackPanel>
<TextBox x:Name="text1" Text="{Binding MyTextUC1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox />
</StackPanel>
UserControl1.xaml.cs
public UserControl1()
{
InitializeComponent();
this.DataContext = this;
}
public static readonly DependencyProperty MyTextUC1Property =
DependencyProperty.Register("MyTextUC1", typeof(string), typeof(UserControl1));
public string MyTextUC1
{
get { return (string)GetValue(MyTextUC1Property); }
set { SetValue(MyTextUC1Property, value); }
}
MainWindwo.xaml
<StackPanel>
<local:UserControl1 x:Name="text1"/>
<TextBlock Text="{Binding ElementName=text1, Path=MyTextUC1}" />
</StackPanel>
On running this, text changes in the TextBox of UserControl1 got reflected in TextBlock of MainWindow.
Not Working:
UserControl1 >> UserControl2 >> MainWindow
Created a UserControl(usercontrol1) and included it in another UserControl(usercontrol2) and then included the UserControl2 in mainWindow.
UserControl1.xaml & UserControl1.xaml.cs
//Same as Working Code
UserControl2.xaml
<Grid>
<local:UserControl1 x:Name="text2" MyTextUC1="{Binding MyTextUC2,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
UserControl2.xaml.cs
public UserControl2()
{
InitializeComponent();
this.DataContext = this;
}
public static readonly DependencyProperty MyTextUC2Property =
DependencyProperty.Register("MyTextUC2", typeof(string), typeof(UserControl2));
public string MyTextUC2
{
get { return (string)GetValue(MyTextUC2Property); }
set { SetValue(MyTextUC2Property, value); }
}
MainWindwo.xaml
<StackPanel>
<local:UserControl2 x:Name="text1"/> //UserControl2 contains UserControl1
<TextBlock Text="{Binding ElementName=text1, Path=MyTextUC2}" />
</StackPanel>
On Running this, text changes in the TextBox of UserControl2 NOT REFLECTED in TextBlock of MainWindow.
I'm pretty sure that this is a basic thing in WPF and I may be implementing this in wrong way. Can someone suggest me where I am wrong? Thanks in advance.
If you check the Debug Output you will see the following error:
System.Windows.Data Error: 40 : BindingExpression path error: 'MyTextUC2' property not found on 'object' ''UserControl1' (Name='text2')'. BindingExpression:Path=MyTextUC2; DataItem='UserControl1' (Name='text2'); target element is 'UserControl1' (Name='text2'); target property is 'MyTextUC1' (type 'String')
The problem is with this binding:
<local:UserControl1 x:Name="text2" MyTextUC1="{Binding MyTextUC2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
It is looking for the MyTextUC2 property in UserControl1. It is because you set the DataContext in UserControl1's constructor to this (UserControl1). So every binding on UserControl1 will be evaluated against UserControl1's DataContext (which is itself).
If you change it to this:
<local:UserControl1 x:Name="text2"
MyTextUC1="{Binding MyTextUC2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type local:UserControl2}}}" />
it should start working.

How do I bind a custom UserControl to the current item inside a ItemTemplate?

I have a ListBox that is data bound to an ObservableCollection.
Inside the DataTemplate, I have this custom user control that I want to bind to the current item.
How do I do that?
What should the binding path be?
Model:
private ObservableCollection<MyItem> _items;
public ObservableCollection<MyItem> Items
{
get { return _items; }
set
{
if (_items.Equals(value))
return;
_items = value;
RaisePropertyChanged("Items");
}
}
XAML:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock TextWrapping="Wrap" Text="{Binding Id}" Margin="2"/>
<TextBlock TextWrapping="Wrap" Text="{Binding Name}" Margin="2"/>
<uc:MyControl MyValue="{Binding <WHATSHOULDTHISBE>, Mode=TwoWay}" Margin="2"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
User control:
public partial class MyControl : UserControl
{
public MyItem MyValue
{
get { return (MyItem)GetValue(MyProperty); }
set { SetValue(MyProperty, value); }
}
public static readonly DependencyProperty MyProperty = DependencyProperty.Register("MyValue",
typeof(MyItem), typeof(MyControl),
new PropertyMetadata(
new PropertyChangedCallback(PropertyChanged)));
public MyControl()
{
InitializeComponent();
}
private static void PropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
MyControl c = obj as MyControl;
if (c != null)
{
// TODO: do something here
}
}
}
MyControl.DataContext property would already be bound to your item view model in this case.
Your MyItem is the data context - if you want to bind to some member of My Item then its:
Binding Path=my_item_member
To bind to MyItem in its entirety I think you want:
Binding Path=.
Your might also need a DataTemplate for your control (in addition to the you have for the ListBoxItems) that maps the MyItems members to your control fields.

Categories