Does anyone know how to make a custom ItemsSource?
What I want to do is to make an itemsSource to my own UserControl so that it could be bound by ObservableCollection<>.
Also, I could know Whenever the number of items in the itemsSource updated, so as to do further procedures.
Thank you so much.
You may need to do something like this in your control
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl1), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));
private static void OnItemsSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as UserControl1;
if (control != null)
control.OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
// Remove handler for oldValue.CollectionChanged
var oldValueINotifyCollectionChanged = oldValue as INotifyCollectionChanged;
if (null != oldValueINotifyCollectionChanged)
{
oldValueINotifyCollectionChanged.CollectionChanged -= new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
// Add handler for newValue.CollectionChanged (if possible)
var newValueINotifyCollectionChanged = newValue as INotifyCollectionChanged;
if (null != newValueINotifyCollectionChanged)
{
newValueINotifyCollectionChanged.CollectionChanged += new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
}
void newValueINotifyCollectionChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//Do your stuff here.
}
Use a DependencyProperty ItemsSource in your CustomControl and then bind to this DependencyProperty
This is the XAML-Code (Recognize the DataContext of the ListBox):
<UserControl
x:Name="MyControl">
<ListBox
DataContext="{Binding ElementName=MyControl}"
ItemsSource="{Binding ItemsSource}">
</ListBox>
</UserControl>
This is the CodeBehind:
public partial class MyCustomControl
{
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable),
typeof(ToolboxElementView), new PropertyMetadata(null));
}
This is the Code, where you use your "MyCustomControl":
<Window>
<local:MyCustomControl
ItemsSource="{Binding MyItemsIWantToBind}">
</local:MyCustomControl>
</Window>
Simplified answer.
public IEnumerable ItemsSource
{
get => (IEnumerable)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl1), new PropertyMetadata(null, (s, e) =>
{
if (s is UserControl1 uc)
{
if (e.OldValue is INotifyCollectionChanged oldValueINotifyCollectionChanged)
{
oldValueINotifyCollectionChanged.CollectionChanged -= uc.ItemsSource_CollectionChanged;
}
if (e.NewValue is INotifyCollectionChanged newValueINotifyCollectionChanged)
{
newValueINotifyCollectionChanged.CollectionChanged += uc.ItemsSource_CollectionChanged;
}
}
}));
private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Logic Here
}
// Do Not Forget To Remove Event On UserControl Unloaded
private void UserControl1_Unloaded(object sender, RoutedEventArgs e)
{
if (ItemsSource is INotifyCollectionChanged incc)
{
incc.CollectionChanged -= ItemsSource_CollectionChanged;
}
}
Related
In WPF, is it possible to get the ItemContainerGenerator.StatusChanged event of an ItemsControl as an MVVM pattern command?
You can get the ItemsControl directly and register the StatusChanged event
I wonder if it is possible to implement the ItemsControl in MVVM pattern without direct access.
Try this:
public class ItemContainerGeneratorBehavior
{
public static Dictionary<ItemsControl, EventHandler> HandlersMap = new Dictionary<ItemsControl, EventHandler>();
public static Dictionary<ItemsControl, GeneratorStatus> StatusMap = new Dictionary<ItemsControl, GeneratorStatus>();
public static ICommand GetStatusCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(StatusCommandProperty);
}
public static void SetStatusCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(StatusCommandProperty, value);
}
// Using a DependencyProperty as the backing store for StatusCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StatusCommandProperty =
DependencyProperty.RegisterAttached("StatusCommand", typeof(ICommand), typeof(ItemContainerGeneratorBehavior),
new PropertyMetadata(null, OnStatusCommandChanged));
private static void OnStatusCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var itemsControl = d as ItemsControl;
if (itemsControl == null)
return;
if (e.OldValue != null)
{
if (HandlersMap.ContainsKey(itemsControl))
{
itemsControl.ItemContainerGenerator.StatusChanged -= HandlersMap[itemsControl];
HandlersMap.Remove(itemsControl);
StatusMap.Remove(itemsControl);
}
}
if (e.NewValue != null)
{
HandlersMap[itemsControl] = (_d, _e) => ItemContainerGenerator_StatusChanged(itemsControl, _e);
StatusMap[itemsControl] = itemsControl.ItemContainerGenerator.Status;
itemsControl.ItemContainerGenerator.StatusChanged += HandlersMap[itemsControl];
}
}
private static void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
var itemsControl = sender as ItemsControl;
if (itemsControl == null)
return;
var commandHandler = GetStatusCommand(itemsControl);
var status = itemsControl.ItemContainerGenerator.Status;
var args = new StatusChangedArgs(StatusMap[itemsControl], status);
StatusMap[itemsControl] = status;
if (commandHandler.CanExecute(args))
commandHandler.Execute(args);
}
}
...which you can then use in your ItemsControl style...
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.Style>
<Style TargetType="{x:Type ItemsControl}" BasedOn="{StaticResource {x:Type ItemsControl}}">
<Setter Property="behaviors:ItemContainerGeneratorBehavior.StatusCommand" Value="{Binding RelativeSource={RelativeSource Self}, Path=DataContext.StatusChangedCommand}" />
</Style>
</ItemsControl.Style>
</ItemsControl>
...and handle in your view model...
private ICommand _StatusChangedCommand;
public ICommand StatusChangedCommand => this._StatusChangedCommand ?? (this._StatusChangedCommand = new RelayCommand<StatusChangedArgs>(OnStatusChanged));
private void OnStatusChanged(StatusChangedArgs args)
{
// do something here
}
You'll also need this:
public class StatusChangedArgs
{
public GeneratorStatus OldStatus { get; private set; }
public GeneratorStatus NewStatus { get; private set; }
public StatusChangedArgs(GeneratorStatus oldStatus, GeneratorStatus newStatus)
{
this.OldStatus = oldStatus;
this.NewStatus = newStatus;
}
public override string ToString()
{
return $"{this.OldStatus} -> {this.NewStatus}";
}
}
I try to use this xaml, to apply an event to command binding:
<telerik:RadGridView x:Name="xRadGridView"
prismcommands:SelectionChangedCommand.Command="{Binding SelectPersonCommand}"
ItemsSource="{Binding GridItems, Mode=TwoWay}">
</telerik:RadGridView>
I get the error:
'SelectionChangedCommand.Command' property is read-only and cannot be
set from markup.
I can bind to prismcommands:RowEditEndedCommand.Command with no problem.
Is there any chance to bind to SelectionChangedCommand.Command?
I use the same PrismCommands in a Silverlight project and it works there.
namespace RadEventToCommand.WPF.PrismCommands
{
public class RowEditEndedCommandBehavior : CommandBehaviorBase<RadGridView>
{
public RowEditEndedCommandBehavior(RadGridView gridView)
: base(gridView)
{
gridView.RowEditEnded +=new EventHandler<GridViewRowEditEndedEventArgs>(gridView_RowEditEnded);
}
void gridView_RowEditEnded(object sender, GridViewRowEditEndedEventArgs e)
{
CommandParameter = e;
ExecuteCommand();
}
}
}
--
namespace RadEventToCommand.WPF.PrismCommands
{
public static class SelectionChangedCommand
{
private static readonly DependencyProperty SelectionChangedCommandBehaviorProperty
= DependencyProperty.RegisterAttached(
"SelectionChangedCommandBehavior",
typeof(SelectionChangedCommandBehavior),
typeof(SelectionChangedCommand),
null);
public static readonly DependencyProperty CommandProperty
= DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(SelectionChangedCommand),
new PropertyMetadata(OnSetCommandCallback));
public static readonly DependencyProperty CommandParameterProperty
= DependencyProperty.RegisterAttached(
"CommandParameter",
typeof(object),
typeof(SelectionChangedCommand),
new PropertyMetadata(OnSetCommandParameterCallback));
public static ICommand GetCommand(RadGridView gridView)
{
return gridView.GetValue(CommandProperty) as ICommand;
}
public static void SetCommandParameter(RadGridView gridView, object parameter)
{
gridView.SetValue(CommandParameterProperty, parameter);
}
public static object GetCommandParameter(RadGridView gridView)
{
return gridView.GetValue(CommandParameterProperty);
}
private static void OnSetCommandCallback
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
RadGridView gridView = dependencyObject as RadGridView;
if (gridView != null)
{
SelectionChangedCommandBehavior behavior = GetOrCreateBehavior(gridView);
behavior.Command = e.NewValue as ICommand;
}
}
private static void OnSetCommandParameterCallback
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
RadGridView gridView = dependencyObject as RadGridView;
if (gridView != null)
{
SelectionChangedCommandBehavior behavior = GetOrCreateBehavior(gridView);
behavior.CommandParameter = e.NewValue;
}
}
private static SelectionChangedCommandBehavior GetOrCreateBehavior(RadGridView gridView)
{
SelectionChangedCommandBehavior behavior =
gridView.GetValue(SelectionChangedCommandBehaviorProperty) as SelectionChangedCommandBehavior;
if (behavior == null)
{
behavior = new SelectionChangedCommandBehavior(gridView);
gridView.SetValue(SelectionChangedCommandBehaviorProperty, behavior);
}
return behavior;
}
}
}
--
namespace RadEventToCommand.WPF.PrismCommands
{
public class RowEditEndedCommandBehavior : CommandBehaviorBase<RadGridView>
{
public RowEditEndedCommandBehavior(RadGridView gridView)
: base(gridView)
{
gridView.RowEditEnded +=new EventHandler<GridViewRowEditEndedEventArgs>(gridView_RowEditEnded);
}
void gridView_RowEditEnded(object sender, GridViewRowEditEndedEventArgs e)
{
CommandParameter = e;
ExecuteCommand();
}
}
}
--
namespace RadEventToCommand.WPF.PrismCommands
{
public static class RowEditEndedCommand
{
private static DependencyProperty RowEditEndedCommandBehaviorProperty
= DependencyProperty.RegisterAttached(
"RowEditEndedCommandBehavior",
typeof(RowEditEndedCommandBehavior),
typeof(RowEditEndedCommand),
null);
public static DependencyProperty CommandProperty
= DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(RowEditEndedCommand),
new PropertyMetadata(OnSetCommandCallback));
public static DependencyProperty CommandParameterProperty
= DependencyProperty.RegisterAttached(
"CommandParameter",
typeof(object),
typeof(RowEditEndedCommand),
new PropertyMetadata(OnSetCommandParameterCallback));
public static ICommand GetCommand(RadGridView gridView)
{
return gridView.GetValue(CommandProperty) as ICommand;
}
public static void SetCommand(RadGridView gridView, object parameter)
{
gridView.SetValue(CommandProperty, parameter);
}
public static void SetCommandParameter(RadGridView gridView, object parameter)
{
gridView.SetValue(CommandParameterProperty, parameter);
}
public static object GetCommandParameter(RadGridView gridView)
{
return gridView.GetValue(CommandParameterProperty);
}
private static void OnSetCommandCallback
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
RadGridView gridView = dependencyObject as RadGridView;
if (gridView != null)
{
RowEditEndedCommandBehavior behavior = GetOrCreateBehavior(gridView);
behavior.Command = e.NewValue as ICommand;
}
}
private static void OnSetCommandParameterCallback
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
RadGridView gridView = dependencyObject as RadGridView;
if (gridView != null)
{
RowEditEndedCommandBehavior behavior = GetOrCreateBehavior(gridView);
behavior.CommandParameter = e.NewValue;
}
}
private static RowEditEndedCommandBehavior GetOrCreateBehavior(RadGridView gridView)
{
RowEditEndedCommandBehavior behavior =
gridView.GetValue(RowEditEndedCommandBehaviorProperty) as RowEditEndedCommandBehavior;
if (behavior == null)
{
behavior = new RowEditEndedCommandBehavior(gridView);
gridView.SetValue(RowEditEndedCommandBehaviorProperty, behavior);
}
return behavior;
}
}
}
I had the source for the behavior copied over from a Silverlight project. It worked there. For some reason in WPF I need the additional method in SelectionChangedCommand
public static void SetCommand(RadGridView gridView, object parameter)
{
gridView.SetValue(CommandProperty, parameter);
}
I copied the code over to check if I could use a common codebase for Silverlight and WPF.
For the RadGridView, we are using the Interaction Triggers. The below code works for us.
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
I have usercontrol, and there is a DependencyProperty defined in it.
#region ImageUri
public static readonly DependencyProperty ImageUriProperty = DependencyProperty.Register(
"ImageUri",
typeof(string),
typeof(ScrollableCanvas),
new PropertyMetadata(new PropertyChangedCallback(ImageUriPropertyChangedCallback)));
private static void ImageUriPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ScrollableCanvas main = sender as ScrollableCanvas;
if (main != null)
{
main.ImageUri = (string)e.NewValue;
}
}
public string ImageUri
{
get
{
return (string)GetValue(ImageUriProperty);
}
set
{
SetValue(ImageUriProperty, value);
UpdateImage();
}
}
#endregion
In the Xaml, I bind a value to it like this
<my:ScrollableCanvas Name="scrollableCanvas1" ImageUri="{Binding Path=LayerImage}" />
when I update the LayerImage in the viewmodel, the ImageUri property does not update.
Can some help on this? Thanks.
BTW: The value is updated when I set the LayerImage in the constructor of the viewmodel.
You shouldn't include your UpdateImage call in your setter, but rather in the property changed callback.
public static readonly DependencyProperty ImageUriProperty = DependencyProperty.Register(
"ImageUri",
typeof(string),
typeof(ScrollableCanvas),
new PropertyMetadata(new PropertyChangedCallback(ImageUriPropertyChangedCallback)));
private static void ImageUriPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ScrollableCanvas main = sender as ScrollableCanvas;
if (main != null)
{
// Since ImageUri has already been called at this point, you can just update your image here...
main.UpdateImage();
}
}
public string ImageUri
{
get
{
return (string)GetValue(ImageUriProperty);
}
set
{
SetValue(ImageUriProperty, value);
}
}
Suppose I have usercontrol with textbox, combobox, button,... inside this control.
Button1 is bound to a ICommand in view model.
My request is: when user hit Enter key in any field, like any textbox, combobox, it will fire Button1 Click event, so that ICommand will be called.
How to implement this?
I created simple behaviour for this kind of situation
<TextBox Grid.Row="2" x:Name="Tags">
<i:Interaction.Behaviors>
<this:KeyEnterCommand Command="{Binding AddTagsCommand}" CommandParameter="{Binding ElementName=Tags}" />
</i:Interaction.Behaviors>
</TextBox>
behaviour code:
public class KeyEnterCommand : Behavior<Control>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.KeyDown += KeyDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.KeyDown -= KeyDown;
}
void KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Enter && Command != null)
{
Command.Execute(CommandParameter);
}
}
#region Command (DependencyProperty)
/// <summary>
/// Command
/// </summary>
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(KeyEnterCommand),
new PropertyMetadata(null));
#endregion
#region CommandParameter (DependencyProperty)
/// <summary>
/// CommandParameter
/// </summary>
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(KeyEnterCommand),
new PropertyMetadata(null));
#endregion
}
Add a dependency property to the UserControl:-
public ICommand EnterKeyCommand
{
get { return GetValue(EnterKeyCommandProperty) as ICommand; }
set { SetValue(EnterKeyCommandProperty, value); }
}
public static readonly DependencyProperty EnterKeyCommandProperty =
DependencyProperty.Register(
"EnterKeyCommand",
typeof(ICommand),
typeof(MyControl),
null);
Attach a handler for the Keyup event on the UserControl using the AddHandler method:-
void MyControl()
{
InitializeComponent();
this.AddHandler(UIElement.KeyUpEvent, new KeyEventHandler(UserControl_KeyUp), true); //Note that last parameter important
}
void UserControl_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && EnterKeyCommand != null && EnterKeyCommand.CanExecute(null))
{
EnterKeyCommand.Execute(null);
}
}
Note the point here is that the use of AddHandler allows you to intercept an event that has already been handled.
Also note that this is simplified for clarity. In reality you would also want to implement another dependency property for the Command parameter and pass that to CanExecute and Execute instead of null. You would also need to detect whether the OriginalSource is a TextBox that has AcceptsReturn set to true.
How do you implement a Silverlight 4 command to execute when the user control loads instead of being mapped to an explicit button click?
Or simply add a trigger in xaml for your UserControl:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<si:InvokeDataCommand Command="{Binding MyCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Create a DependencyProperty of type ICommand:-
#region public ICommand LoadedCommand
public ICommand LoadedCommand
{
get { return GetValue(LoadedCommandProperty) as ICommand; }
set { SetValue(LoadedCommandProperty, value); }
}
public static readonly DependencyProperty LoadedCommandProperty =
DependencyProperty.Register(
"LoadedCommand",
typeof(ICommand),
typeof(MainPage),
new PropertyMetadata(null));
#endregion public ICommand LoadedCommand
Also add a something to act as the command parameter:-
#region public object LoadedCommandParameter
public object LoadedCommandParameter
{
get { return GetValue(LoadedCommandParameterProperty) as object; }
set { SetValue(LoadedCommandParameterProperty, value); }
}
public static readonly DependencyProperty LoadedCommandParameterProperty =
DependencyProperty.Register(
"LoadedCommandParameter",
typeof(object),
typeof(MainPage),
new PropertyMetadata(null));
#endregion public object LoadedCommandParameter
Now set up its execution like this:-
public UserControl1()
{
InitializeComponent();
Loaded += UserControl1_Loaded;
}
void UserControl1_Loaded(object sender, RoutedEventArgs e)
{
if (LoadedCommand != null && LoadedCommand.CanExecute(LoadedCommandParameter))
{
LoadedCommand.Execute(LoadedCommandParameter);
}
}
Now if your ViewModel (has a command called StartStuff) then:-
<UserControl1 LoadedCommand="{Binding StartStuff}" .... >
Thats a load of code. The concise version without the code behind
public class LoadedBehaviour
{
public static ICommand GetLoadedCommand(DependencyObject dependencyObject)
{
return (ICommand)dependencyObject.GetValue(LoadedCommandProperty);
}
public static void SetLoadedCommand(DependencyObject dependencyObject, ICommand value)
{
dependencyObject.SetValue(LoadedCommandProperty, value);
}
public static Action GetLoadedCommandExecutor(DependencyObject dependencyObject)
{
return (Action)dependencyObject.GetValue(LoadedCommandExecutorProperty);
}
public static void SetLoadedCommandExecutor(DependencyObject dependencyObject, Action value)
{
dependencyObject.SetValue(LoadedCommandExecutorProperty, value);
}
public static readonly DependencyProperty LoadedCommandProperty = DependencyProperty.Register("LoadedCommand", typeof(ICommand), typeof(FrameworkElement), new PropertyMetadata(OnPropertyChanged));
public static readonly DependencyProperty LoadedCommandExecutorProperty = DependencyProperty.Register("LoadedCommandExecutor", typeof(Action), typeof(FrameworkElement), new PropertyMetadata(OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is FrameworkElement))
{
throw new ArgumentException("Loaded command can only be used on FrameworkElements");
var executor = GetLoadedCommandExecutor(d);
if(executor == null)
{
executor = () =>
{
var command = GetLoadedCommand(d);
command.Execute(e);
};
SetLoadedCommandExecutor(d, executor);
((FrameworkElement)d).Loaded += (obj, args) => executor();
}
}
}