So I have an ItemsControl set in my xaml as such:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<local:ToggleButton Command="{Binding DataContext.ItemSelectedCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type WrapPanel}}}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid}}}"
Text="{Binding DataContext.ItemEnum, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid}}, Converter={StaticResource EnumToStringConverter}}"
IsActive="{Binding DataContext.Selected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid}}, UpdateSourceTrigger=PropertyChanged}"
Width="96"
Height="88"
Margin="5" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have the 4 dependency properties Command, CommandParameter, Text, and IsActive.
The first 3 dependency properties work correctly, the text is set, and the command callback works with the parameter.
The IsActive property however does NOT work.
The Items property in the main viewmodel is defined as:
List<ItemViewModel> Items { get; set; }
The ItemViewModel is defined as:
public class ItemViewModel : INotifyPropertyChanged
{
public ItemViewModel()
{
this.Selected = true;
}
private bool? _selected;
public event PropertyChangedEventHandler PropertyChanged;
public ItemEnum ItemEnum { get; set; }
public bool? Selected
{
get { return this._selected; }
set
{
this._selected = value;
this.OnPropertyChanged(nameof(this.Selected));
}
}
protected virtual void OnPropertyChanged(string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
the dependency property for the IsActive property in the ToggleButton.xaml.cs file looks like:
public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register(nameof(IsActive), typeof(bool?), typeof(ToggleButton), new PropertyMetadata(null, IsActiveSetCallback));
private static void IsActiveSetCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var button = (ToggleButton)obj;
button.IsActive = (bool)(e.NewValue ?? e.OldValue);0xa5, 0xa5));
}
My command callback in the main view model looks like this:
this.ItemSelectedCommand =
new DelegateCommand(
itemVm =>
{
bool? setTo = !((ItemViewModel) itemVm).Selected;
this.Items.ForEach(i => i.Selected = false);
((ItemViewModel) itemVm).Selected = setTo;
});
Again, the other dependency properties (defined basically identically to IsActiveProperty) work correctly, so when I click the item, the above command gets called (verified by breakpoint), the item's Selected flag gets toggled properly, but the IsActiveSetCallback never gets hit. I can't see what I'm doing wrong, but clearly it's something.
Does anyone see something that I don't?
Thanks in advance!
After spending too much time trying to solve this, of course I manage to solve it myself within half an hour of posting this question..
I pulled this from the Microsoft DependencyProperty docs, specifically the section about 'Dependency Property Setting Precedence List':
Local value. A local value might be set through the convenience of the "wrapper" property, which also equates to setting as an attribute or property element in XAML, or by a call to the SetValue API using a property of a specific instance. If you set a local value by using a binding or a resource, these each act in the precedence as if a direct value was set.
So, I was setting this.Active in two places in the ToggleButton.xaml.cs, once in the constructor as a default value, and once in the IsActiveSetCallback .. turns out by doing that, I was overriding the binding, and neither of those calls were necessary anyways. So simple!
Related
I have an ItemsControl that should display the values of some properties of an object.
The ItemsSource of the ItemsControl is an object with two properties: Instance and PropertyName.
What I am trying to do is displaying all the property values of the Instance object, but I do not find a way to set the Path of the binding to the PropertyName value:
<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Source=??{Binding Path=Instance}??, Path=??PropertyName??, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
the question marks are the points where I don't know how to create the binding.
I initially tried with a MultiValueConverter:
<TextBlock Grid.Column="1" Text="{Binding}">
<TextBlock.DataContext>
<MultiBinding Converter="{StaticResource getPropertyValue}">
<Binding Path="Instance" Mode="OneWay"/>
<Binding Path="PropertyName" Mode="OneWay"/>
</MultiBinding>
</TextBlock.DataContext>
</TextBlock>
The MultiValueConverter uses Reflection to look through the Instance and returns the value of the property.
But if the property value changes, this change is not notified and the displayed value remains unchanged.
I am looking for a way to do it with XAML only, if possible, if not I will have to write a wrapper class to for the items of the ItemsSource collection, and I know how to do it, but, since it will be a recurring task in my project, it will be quite expensive.
Edit:
For those who asked, InstanceProperties is a property on the ViewModel which exposes a collection of objects like this:
public class InstanceProperty : INotifyPropertyChanged
{
//[.... INotifyPropertyChanged implementation ....]
public INotifyPropertyChanged Instance { get; set; }
public string PropertyName { get; set; }
}
Obviously the two properties notify theirs value is changing through INotifyPropertyChanged, I don't include the OnPropertyChanged event handling for simplicity.
The collection is populated with a limited set of properties which I must present to the user, and I can't use a PropertyGrid because I need to filter the properties that I have to show, and these properties must be presented in a graphically richer way.
Thanks
Ok, thanks to #GazTheDestroyer comment:
#GazTheDestroyer wrote: I cannot think of any way to dynamically iterate and bind to an arbitrary object's properties in XAML only. You need to write a VM or behaviour to do this so you can watch for change notifications, but do it in a generic way using reflection you can just reuse it throughout your project
I found a solution: editing the ViewModel class InstanceProperty like this
added a PropertyValue property
listen to PropertyChanged event on Instance and when the PropertyName value changed is fired, raise PropertyChanged on PropertyValue
When Instance or PropertyName changes, save a reference to Reflection's PropertyInfo that will be used by PropertyValue to read the value
here is the new, complete, ViewModel class:
public class InstanceProperty : INotifyPropertyChanged
{
#region Properties and events
public event PropertyChangedEventHandler PropertyChanged;
private INotifyPropertyChanged FInstance = null;
public INotifyPropertyChanged Instance
{
get { return this.FInstance; }
set
{
if (this.FInstance != null) this.FInstance.PropertyChanged -= Instance_PropertyChanged;
this.FInstance = value;
if (this.FInstance != null) this.FInstance.PropertyChanged += Instance_PropertyChanged;
this.CheckProperty();
}
}
private string FPropertyName = null;
public string PropertyName
{
get { return this.FPropertyName; }
set
{
this.FPropertyName = value;
this.CheckProperty();
}
}
private System.Reflection.PropertyInfo Property = null;
public object PropertyValue
{
get { return this.Property?.GetValue(this.Instance, null); }
}
#endregion
#region Private methods
private void CheckProperty()
{
if (this.Instance == null || string.IsNullOrEmpty(this.PropertyName))
{
this.Property = null;
}
else
{
this.Property = this.Instance.GetType().GetProperty(this.PropertyName);
}
this.RaisePropertyChanged(nameof(PropertyValue));
}
private void Instance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == this.PropertyName)
{
this.RaisePropertyChanged(nameof(PropertyValue));
}
}
private void RaisePropertyChanged(string propertyname)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
#endregion
}
and here is the XAML:
<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Path=PropertyValue, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have a user control that defines an ItemsControl and an ItemTemplate for that control, i.e.,
<ItemsControl Name="ItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Name="SelectionButton" Content="MyButton"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In the code behind I specify a dependency property that enables me to bind the ItemsSource property of the ItemsControl, i.e.,
public static readonly DependencyProperty ButtonSourceProperty = DependencyProperty.Register(
"ButtonSource", typeof(IEnumerable), typeof(MyControl),
new PropertyMetadata(null, new PropertyChangedCallback(OnButtonSourceChanged)));
public IEnumerable ButtonSource
{
get { return (IEnumerable)GetValue(ButtonSourceProperty); }
set { SetValue(ButtonSourceProperty, value); }
}
private static void OnButtonSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var buttonSelectionControl = (ButtonSelectionControl)d;
buttonSelectionControl.ItemsControl.ItemsSource = (IEnumerable)e.NewValue;
}
public static void SetButtonSource(DependencyObject obj, IEnumerable enumerable)
{
obj.SetValue(ButtonSourceProperty, enumerable);
}
public static IEnumerable GetButtonSource(DependencyObject obj)
{
return (IEnumerable)obj.GetValue(ButtonSourceProperty);
}
such that in xaml I can set the source for MyControl as follows
<local:MyControl ButtonSource={Binding MyCollection} \>
This works, but how can I define a dependency property in MyControl that specifies the command to bind to in MyCollection? Currently I have the following declared in xaml for the command binding
Command="{Binding DataContext.MyCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding .}"
How can I abstract this in such a way that I can set the item command to bind to in xaml, something like:
<local:MyControl ButtonSource={Binding MyCollection}
ButtonCommand={Binding MyCommand} \>
Pointers appreciated.
Ensure your UserControl has a dependency property to an ICommand, let's say this is called "ButtonCommand".
You should be able to bind to this inside the template for your control:
<ItemsControl Name="ItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Name="SelectionButton" Content="MyButton"
Command="{Binding ButtonCommand, RelativeSource={RelativeSource AncestorType=wpfApplication1:UserControl1}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The button click should then raise the command set in the "ButtonCommand" dependency property defined in your user control.
Your ButtonCommand would definition (inside the UserControl code) would look like this:
public static readonly DependencyProperty ButtonCommandProperty = DependencyProperty.Register("ButtonCommand", typeof (ICommand), typeof (UserControl1), new PropertyMetadata(default(ICommand)));
public ICommand ButtonCommand { get { return (ICommand) GetValue(ButtonCommandProperty); } set { SetValue(ButtonCommandProperty, value); }}
Creating a command class which implements ICommand is boilerplate stuff as you probably know. By putting this into your button xaml:
CommandParameter="{Binding}"
..it will allow you to work with the item from the list in the command handling code:
public class TheCommand : ICommand
{
public void Execute(object parameter)
{
var yourListItemObject = parameter as yourListItemObjectType;
}
// boilerplate stuff
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
}
You can define 2 dependency properties ButtonCommmand and ButtonCommandParameter in you UserContol's .cs file and bind them in UserControl's xaml like this:
<UserControl x:Class="..."
x:Name="this">
<Button Command="{Binding ButtonCommand, ElementName=this}"
CommandParameter="{Binding ButtonCommandPrameter, ElementName=this}"/>
</UserControl>
I am having a hard time finding the right syntax for binding to a ComboBox's SelectedItem's property. This is the XAML I am trying to use for the binding. Where you see SelectedItem.Mode is the idea I am having difficulty with. Note that CurrentMode is in the ViewModel and has the same type as SelectedItem.Mode
<ComboBox SelectedItem.Mode="{Binding Path=CurrentMode, Mode=TwoWays}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ImageSource}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<local:ModeItem Mode="Free" ImageSource="pencil.png"/>
<local:ModeItem Mode="Arrow" ImageSource="arrow.png"/>
</ComboBox>
A local:ModeItem looks like this
public class ModeItem : DependencyObject, INotifyPropertyChanged
{
public static readonly DependencyProperty ModeProperty = DependencyProperty.Register("Mode", typeof(AnnotationMode), typeof(ModeItem));
public AnnotationMode Mode
{
get { return (AnnotationMode)GetValue(ModeProperty); }
set { SetValue(ModeProperty, value); }
}
public string ImageSource { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
I am using MVVM and trying to bind the AnnotationMode (CurrentMode) of the ViewModel to that of the ComboBox's SelectedItem's AnnotationMode (Mode)
Just do this
SelectedItem="{Binding CurrentMode}
You don't have to do all this extra stuff you are doing. Note You do need to make the datacontext of the combobox is pointing to your viewmodel.
Edit :-
You should be able to do this
SelectedValue="{Binding CurrentMode, Mode=TwoWay}"
SelectedValuePath="Mode"
I'm writing a value input control can be used everywhere. The control itself has a view model which set to its DataContext as usual. But when I use the control in a parent control like:
<UserControl x:Class="X.Y.Z.ParentControl">
...
<local:ValueInput Value="{Binding Path=MyValue}" />
...
</UserControl>
I'm going to bind the MyValue property of ParentControl's DataContext to the ValueInput control, but WPF tell me it cannot find the MyValue property in ValueInputViewModel class, which is the view model of ValueInput control itself. Why WPF is looking for the value from child's DataContext?
I just want to write a control which can be used like this:
<telerik:RadNumericUpDown Value="{Binding Path=NumberValue}" />
The NumberValue property is defined in in the parent's DataContext, not in the control's. This pattern works for teleriks control but not for my control.
What should I do?
For any FrameworkElement, there can be only 1 DataContext.
If UserControl has its own DataContext, it cannot use parent's DataContext.
However you can walk up to parent and get its DataContext (each time you need to reference Parent's DataContext) using RelativeSource
Binding="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.NumberValue}"
For this example to work, Parent (root at any level) should be Window. If it is a UserControl,
Binding="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}, Path=DataContext.NumberValue}"
The code is from this link provided by fiq
My friend told me not to use DataContext as the view model in a standalone control since DataContext would be easily overridden - define a ViewModel property and bind in the XAML could solve the problem. Here's an example:
View model class:
public class MyValueInputViewModel
{
public string MyText { get; set; }
}
Code behind:
public partial class MyValueInput : UserControl
{
public MyValueInput()
{
InitializeComponent();
this.ViewModel = new MyValueInputViewModel
{
MyText = "Default Text"
};
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(MyValueInputViewModel), typeof(MyValueInput));
public MyValueInputViewModel ViewModel
{
get
{
return (MyValueInputViewModel)this.GetValue(ViewModelProperty);
}
private set
{
this.SetValue(ViewModelProperty, value);
}
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(MyValueInput), new PropertyMetadata(OnValuePropertyChanged));
private static void OnValuePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
var input = (MyValueInput)o;
input.ViewModel.MyText = input.Value;
}
public string Value
{
get { return (string)this.GetValue(ValueProperty); }
set { this.SetValue(ValueProperty, value); }
}
}
XAML:
<UserControl x:Class="..." x:Name="Self" ...>
<Grid>
<TextBox Text="{Binding ViewModel.MyText, ElementName=Self, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</UserControl>
I have a Silverlight Templated Control (not a user control), which contains a ListBox.
In the DataTemplate of the ListBox i have a Button, like so:
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ProgressBar Grid.Column="0" Width="70" Height="20" Value="{Binding Path=Percentage}" Minimum="0.0" Maximum="100.0" />
<TextBlock Grid.Column="0" Text="{Binding Path=Percentage, StringFormat='{}{0:##0.0}%'}" Margin="10,3,3,3" HorizontalAlignment="Center" />
<TextBlock Grid.Column="1" Text="{Binding Path=File.Name}" Margin="3" />
<Button Grid.Column="2" Content="Remove" x:Name="RemoveButton" Command="{TemplateBinding DeleteCommand}" Style="{TemplateBinding UploadButtonStyle}" HorizontalAlignment="Right" Margin="0,0,5,0" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
See the button there at the end of the template? HOW CAN I ACCESS IT'S CLICK EVENT? I can't use the GetTemplateChild() method since the button is part of the DataTemplate. I've tried Commanding (as you can see above). Seems like that's the way to go, although the Templated Control isn't exactly MVVM.
Any ideas? Maybe something other than Commanding? or else I'm doing the commanding wrong?
here's some relevant code:
...the Dependency Property / Property definitions... (should it be a Dep Prop?)
public static readonly DependencyProperty DeleteCommandProperty =
DependencyProperty.Register("DeleteCommand", typeof(ICommand), typeof(MultipleFileUpload), new PropertyMetadata(null));
public ICommand DeleteCommand
{
get { return (ICommand)GetValue(DeleteCommandProperty); }
set
{
SetValue(DeleteCommandProperty, value);
FirePropertyChanged("DeleteCommand"); //INotifyPropertyChanged stuff
}
}
... in OnApplyTemplate()...
public override void OnApplyTemplate()
{
....
DeleteCommand = new DelegateCommand(RemoveItemFromList, CanRemove);
....
base.OnApplyTemplate();
}
...the ICommand Action...
private void RemoveItemFromList(object commandParameter)
{
//NEVER GETTING HERE!
}
I hope it's something small.
Thanks people!
Kevin
I've added a command as a property to the class of the objects I bind into ListBoxes's (and other ItemsControl's) ItemSource. This does mean I have to change my "data" objects to handle GUI events - which often seemed wrong and hacky.
I've also derived ItemsControl (but since a listbox is an ItemsControl this may still apply). I add my own properties the derived control that I'll ultimately want to access from the items. In your case the button command handler. It should be easy to set these properties since they aren't locked-up in that nested template.
Next, I overrided GetContainerForItemOverride() in that derived class and return another class, my own derived ContentPresenter. This new ContentPresenter should also have that same command property - set it equal to ItemControl's command in GetContainerForItemOverride when you construct it.
Now in the DataTemplate use TemplateBinding (not regular Binding) to get to that Command.
I've kicked around the item of trying to make a generic/reusable version of all of this.
Edit, basic example :
class MyItemsControl : ItemsControl
{
public Command MyCommand {get;set;} // I've often use a full-blown DP here
snip
protected override DependencyObject GetContainerForItemOverride()
{
return new MyContentPresenter(this.MyCommand); // MyContentPresenter is just a derived ContentPresenter with that same property.
}
Edit again:
I've also put code in ItemsControl.PrepareContainerForItemOverride. This method gives you both the ContentControl (your own one if you're overriding GetContainerForItemOverride) and the current "Item" in the list. In here you can also do further initialization of the ContentControl instance - if what you want to do depends on the object that it's being bound to.
I suggest you use a single relaycommand:
public class RelayCommand<T> : ICommand
{
#region Fields
readonly Action<T> _execute = null;
readonly Predicate<T> _canExecute = null;
#endregion // Fields
#region Constructors
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion // ICommand Members
}
XAML:
<Button Grid.Column="2" Content="Remove" x:Name="RemoveButton" Command="{Binding DeleteCommand}" CommandParameter={Binding} Style="{TemplateBinding UploadButtonStyle}" HorizontalAlignment="Right" Margin="0,0,5,0" />
what this will do is everytime you click on the button, it will invoke the same deletecommand, but will pass the current item as parameter.
Hope this helps
I've come across this idea in MSDN, I have not tried it but I figured it was worth sharing here:
The DataContext of the items in the list box is not the same as the views DataContext. Each item's DataContext refers to an item in the collection that is bound to the list box's ItemsSource property.
A solution is to bind the command property to a static resource and set the value of the static resource to the command you want to bind. This is illustrated in the following XAML from the Stock Trader RI.
<!--Specifying the observablecommand in the view's resources-->
<UserControl.Resources>
<Infrastructure:ObservableCommand x:Key="BuyCommand" />
</UserControl.Resources>
<!—Binding the Button Click to the command. This control can sit inside a datagrid or a list box. -->
<Button Commands:Click.Command="{Binding Path=Value, Source={StaticResource BuyCommand}}" Commands:Click.CommandParameter="{Binding Path=TickerSymbol}" />
Then in the code-behind of the view, you must specify that the value of the resource actually points to the command on the presentation model. The following is an example of this from the Stock Trader RI, where the BuyCommand property on the presentation model is put in the resources.
((ObservableCommand)this.Resources["BuyCommand"]).Value = value != null ? value.BuyCommand : null;
Hi you can use relative source and AncesterType. Then its works fine for me.
Refer the below code.
<Button Content="Delete" Command="{Binding DataContext.DeleteCommand,
RelativeSource= {RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}"
CommandParameter="{Binding Path=SelectedItem, RelativeSource= {RelativeSource FindAncestor, AncestorType=
{x:Type ListBox}}}"/>