I'm trying to implement the example at:
https://github.com/olohmann/WpfRxControls
There are three parts to the custom control:
PART_TextBox
PART_Popup
PART_ListBox
Relevant source:
https://github.com/olohmann/WpfRxControls/blob/master/WpfRxControls/AutoCompleteTextBox.cs
https://github.com/olohmann/WpfRxControls/blob/master/WpfRxControls/Themes/Generic.xaml
All the pieces are in place, and the code using the new control is as follows:
<ctrls:AutoCompleteTextBox
Grid.Row="1"
AutoCompleteQueryResultProvider="{Binding AutoCompleteQueryResultProvider}"
Margin="10" FontSize="20" PopupHeight="300">
</ctrls:AutoCompleteTextBox>
I just need to hook into the ListBox's SelectionChanged event in my pages XAML / ViewModel, how can this be accomplished?
Edit: In XAML / VM, not view code behind. Thus far all view code behinds are empty and I'd like to keep it that way.
I thought there was some way to override PART_ListBox in a ControlTemplate override in MainWindow.XAML?
Edit: Final solution, thanks to mm8
In the AutoCompleteTextBox.cs, create a dependency property of type ICommand:
public const string AutoCompleteSelectionChangedPropertyName = "AutoCompleteSelectionChangedCommand";
public ICommand AutoCompleteSelectionChangedCommand
{
get { return (ICommand) GetValue(AutoCompleteSelectionChangedProperty); }
set { SetValue(AutoCompleteSelectionChangedProperty, value);}
}
public static readonly DependencyProperty AutoCompleteSelectionChangedProperty = DependencyProperty.Register(
AutoCompleteSelectionChangedPropertyName,
typeof(ICommand),
typeof(AutoCompleteTextBox));
In the SetResultText method:
AutoCompleteSelectionChangedCommand?.Execute(autoCompleteQueryResult);
View / ViewModel usage:
<ac:AutoCompleteTextBox Name="AutoComplete"
AutoCompleteQueryResultProvider="{Binding AutoCompleteQueryResultProvider}"
FontSize="12"
AutoCompleteSelectionChangedCommand="{Binding CommandEditValueChanged}">
</ac:AutoCompleteTextBox>
public ICommand CommandEditValueChanged { get; set; }
public MainWindowViewModel(){
CommandEditValueChanged = new DelegateCommand<object>(OnEditValueChanged);
}
private void OnEditValueChanged(object result){
// do stuff
}
You could handle the Loaded event of the AutoCompleteTextBox in the view, get a reference to the PART_ListBox in the control template using the FindName method and then hook up an event handler for the SelectionChanged event of the ListBox:
<ctrls:AutoCompleteTextBox
Grid.Row="1"
AutoCompleteQueryResultProvider="{Binding AutoCompleteQueryResultProvider}"
Margin="10" FontSize="20" PopupHeight="300" Loaded="AutoCompleteTextBox_Loaded">
</ctrls:AutoCompleteTextBox>
private void AutoCompleteTextBox_Loaded(object sender, RoutedEventArgs e)
{
AutoCompleteTextBox actb = sender as AutoCompleteTextBox;
ListBox lb = actb.Template.FindName("PART_ListBox", actb) as ListBox;
if (lb != null)
{
lb.SelectionChanged += (ss, ee) =>
{
MainWindowViewModel vm = DataContext as MainWindowViewModel;
//invoke a command of the view model or do whatever you want here...
var selectedItem = lb.SelectedItem;
};
}
}
Your view model class has no (and shouldn't have any) reference nor knowledge about the ListBox that is part of the template of the control.
I thought there was some way to override PART_ListBox in a ControlTemplate override in MainWindow.XAML?
Then you will have to override/re-define the entire ControlTemplate of the AutoCompleteTextBox control which seems a bit unnecessary.
MVVM is not about eliminating code from the views - it's about separation of concerns and whether you hook up an event handler from the XAML markup of the view or the code-behind of the very same view makes no difference at all as far as the design pattern is concerned.
Edit: But if you want to keep the code-behind classes clean you could implement this using an attached behaviour:
public class AutoCompleteBoxBehavior
{
public static ICommand GetSelectionChangedCommand(AutoCompleteTextBox actb)
{
return (ICommand)actb.GetValue(SelectionChangedCommandProperty);
}
public static void SetSelectionChangedCommand(AutoCompleteTextBox actb, ICommand value)
{
actb.SetValue(SelectionChangedCommandProperty, value);
}
public static readonly DependencyProperty SelectionChangedCommandProperty =
DependencyProperty.RegisterAttached(
"SelectionChangedCommand",
typeof(ICommand),
typeof(AutoCompleteBoxBehavior),
new UIPropertyMetadata(null, OnHandleSelectionChangedEvent));
private static void OnHandleSelectionChangedEvent(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ICommand command = e.NewValue as ICommand;
if(command != null)
{
AutoCompleteTextBox actb = d as AutoCompleteTextBox;
actb.Loaded += (ss, ee) =>
{
ListBox lb = actb.Template.FindName("PART_ListBox", actb) as ListBox;
if (lb != null)
{
lb.SelectionChanged += (sss, eee) =>
{
command.Execute(null);
};
}
};
}
}
}
<ctrls:AutoCompleteTextBox
Grid.Row="1"
AutoCompleteQueryResultProvider="{Binding AutoCompleteQueryResultProvider}"
Margin="10" FontSize="20" PopupHeight="300"
local:AutoCompleteBoxBehavior.SelectionChangedCommand="{Binding YourCommand}">
</ctrls:AutoCompleteTextBox>
Introduction to Attached Behaviors in WPF: https://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF
Related
In my VM, I have an event
public class ViewModelBase
{
public delegate bool MyDel(object param);
public MyDel MyEvent;
public void TransferClick()
{
MyEvent(null); // to simulate the click at View
}
}
And in the View, currently I have the following code ( behind):
public class View: UserControl
{
private void UserControl1_Load(Object sender, EventArgs e)
{
(DataContext as ViewModelBase).MyEvent+=SimulateClick;
}
private bool SimulateClick(object param)
{
//some logic to simulate clicks on the View, on the user control
}
}
So that the VM can invoke the SimulateClick logic in View whenever it has to.
I don't like this approach because it pollutes my view's code behind. Any way to make the MyEvent bind to XAML instead, much like how I bind VM ICommand to existing button clicks and stuff like that?
Note: I don't actually want to simulate mouse clicks ( I know I can use ICommand to do just that), just want to do some events like mouse clicks event on my MVVM model.
If you view model needs to tell the view to do something you could use an event aggregator or a messenger to send a message from the view model to the view in a loosely coupled way:
https://blog.magnusmontin.net/2014/02/28/using-the-event-aggregator-pattern-to-communicate-between-view-models/
https://msdn.microsoft.com/en-us/magazine/jj694937.aspx
The benefit of using this pattern is that the view model and the view don't need to know anything about each other.
The other option would be to inject the view model with an interface type that the view implements:
public interface IView
{
bool SimulateClick(object param);
}
public partial class View : UserControl, IView
{
public View()
{
InitializeComponent();
DataContext = new ViewModel(this);
}
public bool SimulateClick(object param)
{
//...
}
}
This doesn't really break the MVVM pattern as the view model only has a dependency upon an interface that the view happens to implement.
Updated answer
First of all - I would highly recommend the approach #mm8 has suggested, or exposing Command(s) (such as RefreshCommand) on your views to achieve the same.
But if that is not an option; then I believe you can create a custom attached event that can technically bind the view-model's event to the control's eventhandler; while maintaining the MVVM level of separation.
For example, you can define an attached event in following manner:
// ViewModel event args
public class MyEventArgs : EventArgs
{
public object Param { get; set; }
}
// Interim args to hold params during event transfer
public class InvokeEventArgs : RoutedEventArgs
{
public InvokeEventArgs(RoutedEvent e) : base(e) { }
public object Param { get; set; }
}
// Base view model
public class ViewModelBase
{
public event EventHandler<MyEventArgs> MyEvent1;
public event EventHandler<MyEventArgs> MyEvent2;
public void TransferClick1()
{
MyEvent1?.Invoke(this, new MyEventArgs { Param = DateTime.Now }); // to simulate the click at View
}
public void TransferClick2()
{
MyEvent2?.Invoke(this, new MyEventArgs { Param = DateTime.Today.DayOfWeek }); // to simulate the click at View
}
}
// the attached behavior that does the magic binding
public class EventMapper : DependencyObject
{
public static string GetTrackEventName(DependencyObject obj)
{
return (string)obj.GetValue(TrackEventNameProperty);
}
public static void SetTrackEventName(DependencyObject obj, string value)
{
obj.SetValue(TrackEventNameProperty, value);
}
public static readonly DependencyProperty TrackEventNameProperty =
DependencyProperty.RegisterAttached("TrackEventName",
typeof(string), typeof(EventMapper), new PropertyMetadata
(null, new PropertyChangedCallback(OnTrackEventNameChanged)));
private static void OnTrackEventNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
FrameworkElement uie = d as FrameworkElement;
if (uie == null)
return;
var eventName = GetTrackEventName(uie);
if (string.IsNullOrWhiteSpace(eventName))
return;
EventHandler<MyEventArgs> vmEventTracker = delegate (object sender, MyEventArgs e) {
Application.Current.Dispatcher.Invoke(() =>
uie.RaiseEvent(new InvokeEventArgs(EventMapper.OnInvokeEvent)
{
Source = sender,
Param = e?.Param
}));
};
uie.DataContextChanged += (object sender, DependencyPropertyChangedEventArgs e) =>
{
var oldVM = e.OldValue;
var newVM = e.NewValue;
if (oldVM != null)
{
var eventInfo = oldVM.GetType().GetEvent(eventName);
eventInfo?.RemoveEventHandler(oldVM, vmEventTracker);
}
if (newVM != null)
{
var eventInfo = newVM.GetType().GetEvent(eventName);
eventInfo?.AddEventHandler(newVM, vmEventTracker);
}
};
var viewModel = uie.DataContext;
if (viewModel != null)
{
var eventInfo = viewModel.GetType().GetEvent(eventName);
eventInfo?.AddEventHandler(viewModel, vmEventTracker);
}
}
public static readonly RoutedEvent OnInvokeEvent =
EventManager.RegisterRoutedEvent("OnInvoke",
RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(EventMapper));
public static void AddOnInvokeHandler(DependencyObject d, RoutedEventHandler handler)
{
FrameworkElement uie = d as FrameworkElement;
if (uie != null)
{
uie.AddHandler(OnInvokeEvent, handler);
}
}
public static void RemoveOnInvokeHandler(DependencyObject d, RoutedEventHandler handler)
{
FrameworkElement uie = d as FrameworkElement;
if (uie != null)
{
uie.RemoveHandler(OnInvokeEvent, handler);
}
}
}
Sample 1 - Event handler
XAML Usage
<StackPanel Margin="20">
<Button Margin="10" Content="Invoke VM event" Click="InvokeEventOnVM" />
<Button Content="View Listener1"
local:EventMapper.TrackEventName="MyEvent1"
local:EventMapper.OnInvoke="SimulateClick1" />
<Button Content="View Listener2"
local:EventMapper.TrackEventName="MyEvent1"
local:EventMapper.OnInvoke="SimulateClick1" />
<Button Content="View Listener3"
local:EventMapper.TrackEventName="MyEvent2"
local:EventMapper.OnInvoke="SimulateClick2" />
</StackPanel>
Sample code-Behind for above XAML:
private void SimulateClick1(object sender, RoutedEventArgs e)
{
(sender as Button).Content = new TextBlock { Text = (e as InvokeEventArgs)?.Param?.ToString() };
}
private void SimulateClick2(object sender, RoutedEventArgs e)
{
SimulateClick1(sender, e);
(sender as Button).IsEnabled = !(sender as Button).IsEnabled; //toggle button
}
private void InvokeEventOnVM(object sender, RoutedEventArgs e)
{
var vm = new ViewModelBase();
this.DataContext = vm;
vm.TransferClick1();
vm.TransferClick2();
}
Sample 2 - Event Trigger (updated 07/26)
XAML Usage
<Button Content="View Listener"
local:EventMapper.TrackEventName="MyEvent2">
<Button.Triggers>
<EventTrigger RoutedEvent="local:EventMapper.OnInvoke">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation AutoReverse="True" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
The VM will receive a command from other VMs ( think of FileVM
notifies tabVM), and then the VM (tabVM) will load the data at the
view via the SimulateClick method at View (tabView).
Why the hell would you name method SimulateClick if the method loads data based on some command? I would refactor your code like this.
public delegate bool MyDel(object data);
public class ViewModelBase
{
public MyDel SomeCommandExecuted;
void SomeCommand_Execute()
{
string[] sampleData = new [] //this may come from the other viewmodel for example
{
"Item1", "Item2", "Items3";
}
MyDel handler = SomeCommandExecuted;
if (handler != null)
{
handler(sampleData);
}
}
}
if you exposed the functionality like this, it is ok to attach to the event in codebehind. Why would you pollute XAML with attaching to the VM's event and calling codebehind method? It's better to attach to the event in codebehind, because at least your code remains type safe and refactorable.
public class View: UserControl
{
public View()
{
this.InitializeComponent();
Loaded += (o, e) => ViewModel.SomeCommandExecuted += ViewModel_SomeCommandExecuted;
}
ViewModelBase ViewModel => (ViewModelBase)DataContext;
private bool ViewModel_SomeCommandExecuted(object data)
{
//load the data into view
}
}
Attaching to the VM's event in codebehind is not violation of MVVM. However, the resposibility of ViewModel is to expose data in such form that is easily consumable from View (usually via databinding).
here is my suggestion:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool InDesignMode => DesignerProperties.GetIsInDesignMode(new DependecyObject());
}
public class ViewModel : ViewModelBase
{
private string[] _data;
//ctor
public ViewModel()
{
if (IsInDesignMode)
{
Data = new [] { "Visible", "In", "XAML", "Designer" }
}
}
public string[] Data
{
get { return _data; }
set { _data = value; OnPropertyChanged(); }
}
void SomeCommand_Execute()
{
string[] sampleData = new [] //this may come from the other viewmodel for example
{
"Item1", "Item2", "Items3";
}
Data = sampleData;
}
}
I have prepared the data in ViewModel so that they are easily consumable in view. Once they are ready, I notify view using PropertyChanged event. Now I can easily bind ItemsControl, ListView, etc in View. No codebehind needed what's so ever. This is the purpose of ViewModel
You can use x:bind for function binding. That way your xaml can bind and directly invoke the view models event handler without needing a "pass through" invoke method in the view.
Click="{x:Bind viewModel.Foo}"
More docs
Here is an example of event binding in wpf
WPF event binding from View to ViewModel?
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<StackPanel Background="Transparent">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<command:EventToCommand
Command="{Binding Main.NavigateToArticleCommand,
Mode=OneWay,
Source={StaticResource Locator}}"
CommandParameter="{Binding Mode=OneWay}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</StackPanel>
Viewmodel
public RelayCommand NavigateToArticleCommand
{
get
{
return _navigateToArticleCommand
?? (_navigateToArticleCommand= new RelayCommand(
async () =>
{
await SomeCommand();
}));
}
}
I have observable collection called (Users) in view model that binded with ListViewControl (lstUsers) in view and what I need is to scroll to current logged in user in List View .
I see in most of examples that used scroll from code behind as following e.g. :
lstUsers.ScrollIntoView(lstUsers[5]);
but what I need is to handle it from view model .
Please advice !
One way of doing this would be to use something like an ICollectionView which has a current item. You can then set IsSynchronizedWithCurrentItem to true to link the current item in the view model to the selected item in the ListView.
Finally handle the event SelectionChanged in the code behind the view to change the scroll position so that it always displays the selected item.
For me the benefit of this method is that the viewmodel is kept unaware of anything about the view which is one of the aims of MVVM. The code behind the view is the perfect place for any code concerning the view only.
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView x:Name="View"
SelectionChanged="Selector_OnSelectionChanged" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Items}"/>
<Button Grid.Row="1" Command="{Binding ChangeSelectionCommand}">Set</Button>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
View.ScrollIntoView(View.SelectedItem);
}
}
public class ViewModel
{
private readonly CollectionViewSource _source = new CollectionViewSource();
public ICollectionView Items
{
get { return _source.View; }
}
public ICommand ChangeSelectionCommand { get; set; }
public ViewModel()
{
SetUp();
ChangeSelectionCommand = new Command(ChangeSelection);
}
private void SetUp()
{
var list = new List<string>();
for (int i = 0; i < 100; i++)
{
list.Add(i.ToString(CultureInfo.InvariantCulture));
}
_source.Source = list;
}
private void ChangeSelection()
{
var random = new Random(DateTime.Now.Millisecond);
var n = random.Next(100);
Items.MoveCurrentToPosition(n);
}
}
public class Command : ICommand
{
private readonly Action _action;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_action();
}
public event EventHandler CanExecuteChanged;
public Command(Action action)
{
_action = action;
}
}
let me share my solution with you
Create your own ListView descendant with dependency property TargetListItem
public class ScrollableListView : ListView
{
/// <summary>
/// Set this property to make ListView scroll to it
/// </summary>
public object TargetListItem
{
get { return (object)GetValue(TargetListItemProperty); }
set { SetValue(TargetListItemProperty, value); }
}
public static readonly DependencyProperty TargetListItemProperty = DependencyProperty.Register(
nameof(TargetListItem), typeof(object), typeof(ScrollableListView), new PropertyMetadata(null, TargetListItemPropertyChangedCallback));
static void TargetListItemPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var owner = (ScrollableListView)d;
owner.ScrollToItem(e.NewValue);
}
public void ScrollToItem(object value)
{
if (value != null && Items != null && Items.Contains(value))
{
ScrollIntoView(value);
}
}
}
create property in ViewModel
object currentListItem;
public object СurrentListItem
{
get => сurrentListItem;
set
{
if (сurrentListItem != value)
{
сurrentListItem = value;
OnPropertyChanged(nameof(СurrentListItem));
}
}
}
bind it
<controls:ScrollableListView ... TargetListItem="{Binding CurrentListItem}"/>
Now you can set CurrentListItem in ViewModel when needed. And the corresponding visual element will become visible in the ListView immediately.
Also maybe you just can use attached property on ListView instead of creating ScrollableListView. But i'm not sure.
Yep, there's always times in MVVM when you need to get at the control. There's various ways of doing this, but here's an easy-ish way of doing it without deriving from the control or messing with routed commands or other such toys what you have in WPF.
In summary:
Create an attached property on your view model.
Set the attached property in XAML to pass the list box back to the view model.
Call .ScrollIntoView on demand.
Note, this is a rough and ready example, make sure your DataContext is set before showing the window.
Code/View Model:
public class ViewModel
{
private ListBox _listBox;
private void ReceiveListBox(ListBox listBox)
{
_listBox = listBox;
}
public static readonly DependencyProperty ListBoxHookProperty = DependencyProperty.RegisterAttached(
"ListBoxHook", typeof (ListBox), typeof (ViewModel), new PropertyMetadata(default(ListBox), ListBoxHookPropertyChangedCallback));
private static void ListBoxHookPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var listBox = (ListBox) dependencyObject;
var viewModel = (ViewModel) listBox.DataContext;
viewModel.ReceiveListBox(listBox);
}
public static void SetListBoxHook(DependencyObject element, ListBox value)
{
element.SetValue(ListBoxHookProperty, value);
}
public static ListBox GetListBoxHook(DependencyObject element)
{
return (ListBox) element.GetValue(ListBoxHookProperty);
}
}
OK, so that will let us get the ListBox passed back to the view; you can do with it as you wish.
Now, just set the property in XAML:
<ListBox wpfApplication1:ViewModel.ListBoxHook="{Binding RelativeSource={RelativeSource Self}}" />
Good to go!
I want to display a list of objects in a LongListSelector, and format them with a DataTemplate. To properly use MVVM I'd like to have a ViewModel in this DataTemplate.
Creating of this ViewModel is no problem, but how do I pass the Item to the ViewModel?
I'm using this code:
<Controls:LongListSelector
ItemsSource="{Binding MyItems}" Margin="0" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch" >
<Controls:LongListSelector.DataContext>
<viewmodel:MyListOfItemsViewModel />
</Controls:LongListSelector.DataContext>
<Controls:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="CurTemplate">
<Grid Margin="10" >
<Grid.DataContext>
<viewmodel:MyViewModel MyItem="{Binding Path=DataContext,ElementName=CurTemplate}" />
</Grid.DataContext>
But alas, the only thing that is set for MyItem is null, and this is never updated to the real value. I found out that later in the process (after the initial setting of MyItem CurTemplate does have a valid DataContext, but this is not sent to my ViewModel. Am I missing something here?
For completeness the code for MyViewModel:
public static DependencyProperty MyItemProperty = DependencyProperty.Register("MyItem", typeof(object), typeof(MyViewModel), new PropertyMetadata("asd", ItemChanged));
private static void ItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
System.Diagnostics.Debugger.Break(); // to set when something is set
// called once, NewValue is null
}
public object MyItem
{
get
{
return (object)GetValue(MyItemProperty);
}
set
{
SetValue(MyItemProperty, value);
RaisePropChangeEvent("MyItem");
}
}
I did a lot of searching and fiddling around, but I'm pretty sure this is just a minor thing that is missing here. I would be very glad if you could help me out here...
EDIT: Solved
I solved my problem by using {Binding Path=Content,RelativeSource={RelativeSource Mode=TemplatedParent}} as binding for the viewmodel. I have no idea why this works with Content, but not with DataContext...
Thanks for your help, robertftw, your linked post brought me to the right track!
Seems like the problem I had a few days ago:
Binding does not update usercontrol property correctly MVVM
public static DependencyProperty MyItemProperty = DependencyProperty.Register("MyItem", typeof(object), typeof(MyViewModel), new PropertyMetadata(string.Empty, ItemChanged));
private static void ItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = d as MyViewModel;
obj.RaisePropChangeEvent("MyItem");
}
The problem I had is that the set of MyItem isn't actually called.
What are you trying to do?
Do you just want to interact to a selection change and have the selected item in the LongListSelector being pushed to the ViewModel?
If so... I'm using an extension for this scenario. The only thing with such an extension is that setting the current item from the ViewModel is not ported back to the view ( but didn't need that ).
The ViewModel change is
public RelayCommand<MyItemType> ViewModelCommand
The XAML change is
<phone:LongListSelector extensions:LongListSelectorExtension.Command="{Binding ViewModelCommand}" />
The extension
public static class LongListSelectorExtension
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand), typeof(LongListSelectorExtension),
new PropertyMetadata(null, OnCommandChanged));
public static ICommand GetCommand(LongListSelector selector)
{
return (ICommand)selector.GetValue(CommandProperty);
}
public static void SetCommand(LongListSelector selector, ICommand value)
{
selector.SetValue(CommandProperty, value);
}
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = d as LongListSelector;
if (selector == null)
{
throw new ArgumentException(
"You must set the Command attached property on an element that derives from LongListSelector.");
}
var oldCommand = e.OldValue as ICommand;
if (oldCommand != null)
{
selector.SelectionChanged -= OnSelectionChanged;
}
var newCommand = e.NewValue as ICommand;
if (newCommand != null)
{
selector.SelectionChanged += OnSelectionChanged;
}
}
private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selector = sender as LongListSelector;
var command = GetCommand(selector);
if (command != null && selector.SelectedItem != null)
{
command.Execute(selector.SelectedItem);
}
selector.SelectedItem = null;
}
}
My question concerns Silverlight (but I guess WPF as well).
Basically I know, how to create dependency property in a user control and how to make it work. But what i was trying to do, and didn't succeded is: to create dependency property (or more than one) in a class, and this class will become a dependency property for my user control.
With other words:
// my UserControl
public class DPTest : UserControl
{
// dependency property, which type is a class, and this class will be holding other dependency properties
public static readonly DependencyProperty GroupProperty =
DependencyProperty.Register("Group", typeof(DPGroup), typeof(DPTest), new PropertyMetadata(new DPGroup(), OnPropertyChanged));
public DPGroup Group
{
get { return (DPGroup)GetValue(GroupProperty); }
set { SetValue(GroupProperty, value); }
}
// this occurs only when property Group will change, but not when a member of property Group will change
static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DPTest g = d as DPTest;
// etc.
}
}
// a class, where I want to hold my dependency properties
public class DPGroup : DependencyObject
{
public static readonly DependencyProperty MyProperty1Property =
DependencyProperty.RegisterAttached("MyProperty1", typeof(int), typeof(DPGroup), new PropertyMetadata(1, OnPropertyChanged));
public int MyProperty1
{
get { return (int)GetValue(MyProperty1Property); }
set { SetValue(MyProperty1Property, value); }
}
// I would like to notify "the parent" (which means user control "DPTest" ), that member MyProperty1 has changed
static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DPTest g = d as DPTest;
if (g != null) g.textBox1.Text = g.Group.MyProperty1.ToString();
}
}
What I want to achieve is to notify (in design time in XAML) a user control DPTest, that member of Group property (Group.MyProperty1) changed it's value. I managed to make it happen in a run-time, for example by using event handler defined in DPGroup class, but this doesn't work in design-time in xaml.
<Grid x:Name="LayoutRoot" Background="White">
<local:DPTest>
<local:DPTest.Group>
<local:DPGroup MyProperty1="2"/>
</local:DPTest.Group>
</local:DPTest>
</Grid>
It works, but only first time, during creating tag:
<local:DPGroup MyProperty1="2"/>
and after this, changing value of MyProperty1, does not fire DPTest.OnPropertyChange. Probably fires DBGroup.OnPropertyChanged, but this of course does not notify user control DPTest about it. So how to make DPTest know, that the Group.MyProperty1 has changed?
I don't want to make any bindings from MyProperty1 to respective property created inside user control DPTest (not to duplicate properties), the point is to have a group of properties in separate class, so i can use this group more than once, like:
// my UserControl
public class DPTest : UserControl
{
public DPGroup Group1 { ... }
public DPGroup Group2 { ... }
}
I see some analogy to UIElement.RenderTransform (let's say it is my Group property) which holds for example ScaleTransform
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RenderTransform>
<ScaleTransform ScaleX="0.4"/>
</Grid.RenderTransform>
</Grid>
ScaleX is an analogy to MyProperty1. The difference is, that changing value of ScaleX (in XAML) will reflect immediate changes in design-time, and exactly this I am trying to achieve.
I was trying to find a solution in entire google/stack overflow and others, but none found. Everywhere are just examples of creating dependency properties inside a user control.
Thank you for your time.
Any help much appreciated.
edit: based on Harlow Burgess answer, a managed to make a working example in Silverlight. I put the whole solution below as an separate answer.
From: http://msdn.microsoft.com/en-us/library/ms752914.aspx#setting_properties_data_binding
Dependency properties, or the DependencyObject class, do not natively
support INotifyPropertyChanged for purposes of producing notifications
of changes in DependencyObject source property value for data binding
operations. For more information on how to create properties for use
in data binding that can report changes to a data binding target, see
Data Binding Overview.
It would be inefficient to design a system that notifies an entire object graph anytime any property of any subproperty (of any subproperty, of any subproperty, ...) changes. So instead you should use Data Binding to specific properties when you need to do something when that property changes, or if you really want to be notified when any subproperty changes, you should implement INotifyPropertyChanged.
How to: Implement Property Change Notification
Example:
public class DPGroup : DependencyObject, INotifyPropertyChanged
{
public static readonly DependencyProperty MyProperty1Property =
DependencyProperty.RegisterAttached(
"MyProperty1",
typeof(int),
typeof(DPGroup),
new PropertyMetadata(1));
public int MyProperty1
{
get { return (int)GetValue(MyProperty1Property); }
set { SetValue(MyProperty1Property, value); }
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
NotifyPropertyChanged(e.Property.Name);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class DPTest : UserControl
{
public static readonly DependencyProperty GroupProperty =
DependencyProperty.Register(
"Group",
typeof(DPGroup),
typeof(DPTest),
new PropertyMetadata(
new DPGroup(),
new PropertyChangedCallback(OnGroupPropertyChanged)
)
);
public DPGroup Group
{
get { return (DPGroup)GetValue(GroupProperty); }
set { SetValue(GroupProperty, value);}
}
static void OnGroupPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DPTest control = (DPTest)d;
DPGroup oldGroup = e.OldValue as DPGroup;
if (oldGroup != null)
{
oldGroup.PropertyChanged -=new PropertyChangedEventHandler(control.group_PropertyChanged);
}
DPGroup newGroup = e.NewValue as DPGroup;
if (newGroup != null)
{
newGroup.PropertyChanged +=new PropertyChangedEventHandler(control.group_PropertyChanged);
}
control.UpdateTextBox();
}
private void group_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.UpdateTextBox();
}
private void UpdateTextBox()
{
this.textBox1.Text = this.Group.MyProperty1.ToString();
}
private TextBox textBox1;
}
Ok, so based on #Harlow Burgess answer, a managed to make a working example in Silverlight.
Basically the difference is, that in SL, DependencyObject class has no OnPropertyChanged method, so in DPGroup class we cannot override it, but we can attach this method in another way, by:
new PropertyMetadata(1, OnPropertyChanged).
So the DPGroup class will look like this:
public class DPGroup : DependencyObject, INotifyPropertyChanged
{
public static readonly DependencyProperty MyProperty1Property =
DependencyProperty.RegisterAttached(
"MyProperty1",
typeof(int),
typeof(DPGroup),
new PropertyMetadata(1, OnPropertyChanged));
public int MyProperty1
{
get { return (int)GetValue(MyProperty1Property); }
set { SetValue(MyProperty1Property, value); }
}
// static method invoked when MyProperty1 has changed value
static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DPGroup g = d as DPGroup;
if (g != null)
{
g.MyProperty1 = (int)e.NewValue;
// invoking event handler, to notify parent class about changed value of DP
if (g.PropertyChanged != null) g.PropertyChanged(g, null);
}
}
// event handler, for use in parent class
public event PropertyChangedEventHandler PropertyChanged;
}
And the parent class, containing dependency property of type DPGroup:
public partial class DPTest : UserControl
{
public static readonly DependencyProperty GroupProperty =
DependencyProperty.Register(
"Group",
typeof(DPGroup),
typeof(DPTest),
new PropertyMetadata(
new DPGroup(),
new PropertyChangedCallback(OnGroupPropertyChanged)
)
);
public DPGroup Group
{
get { return (DPGroup)GetValue(GroupProperty); }
set { SetValue(GroupProperty, value); }
}
// static method invoked when Group property has changed value
// here we need to attach event handler defined if DPGroup, so it will fire from inside Group property,
// when Group.MyProperty1 will change value
static void OnGroupPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DPTest control = (DPTest)d;
DPGroup oldGroup = e.OldValue as DPGroup;
// removing event handler from prevoius instance of DBGroup
if (oldGroup != null)
oldGroup.PropertyChanged -= new PropertyChangedEventHandler(control.group_PropertyChanged);
DPGroup newGroup = e.NewValue as DPGroup;
// adding event handler to new instance of DBGroup
if (newGroup != null)
newGroup.PropertyChanged += new PropertyChangedEventHandler(control.group_PropertyChanged);
DPTest g = d as DPTest;
if (g != null)
control.UpdateTextBox();
}
private void group_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
UpdateTextBox();
}
// here you can do anything with changed value Group.MyProperty1
private void UpdateTextBox()
{
this.textBox1.Text = this.Group.MyProperty1.ToString();
}
public DPTest()
{
InitializeComponent();
}
}
Now, the XAML part for DPTest:
<UserControl x:Class="Silverlight_Workbench_2.DPTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Silverlight_Workbench_2"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" >
<Grid x:Name="LayoutRoot" Background="White">
<TextBox Height="23" HorizontalAlignment="Left" Margin="76,61,0,0"
x:Name="textBox1" VerticalAlignment="Top" Width="120" />
</Grid>
</UserControl>
Finally, we can embed our DPTest in some content of any control, for example in a Grid of another user control:
<UserControl x:Class="Silverlight_Workbench_2.DPTestMain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Silverlight_Workbench_2"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<Grid x:Name="LayoutRoot" Background="White">
<local:DPTest>
<local:DPTest.Group>
<!--here we can change value, and it will be reflected in design window
as a text in textBox1-->
<local:DPGroup MyProperty1="8"/>
</local:DPTest.Group>
</local:DPTest>
</Grid>
</UserControl>
That is all, thanks again to the Harlow Burgess for the help!
Is there a MVVM way to select text in a textbox? The MVVM framework that I am using is Laurent Bugnion's MVVM Light Toolkit.
Whenever I am trying to directly affect the the View in a "pure" MVVM application (no code-behind in View), I will use Attached Properties to encapsulate whatever effect I am trying to achieve. I will create an interface that defines the actions I wish to take using custom events. I then implement this interface in each ViewModel that will be "running" these commands on the View. Finally, I bind my ViewModel to the attached property in my View definition. The following code shows how to this for SelectAll and a TextBox. This code can be easily expanded to perform just about any action on any component in the View.
My Attached Property and interface definition:
using System.Windows;
using System.Windows.Controls;
using System;
using System.Collections.Generic;
namespace SelectAllSample
{
public static class TextBoxAttach
{
public static readonly DependencyProperty TextBoxControllerProperty = DependencyProperty.RegisterAttached(
"TextBoxController", typeof(ITextBoxController), typeof(TextBoxAttach),
new FrameworkPropertyMetadata(null, OnTextBoxControllerChanged));
public static void SetTextBoxController(UIElement element, ITextBoxController value)
{
element.SetValue(TextBoxControllerProperty, value);
}
public static ITextBoxController GetTextBoxController(UIElement element)
{
return (ITextBoxController)element.GetValue(TextBoxControllerProperty);
}
private static readonly Dictionary<ITextBoxController, TextBox> elements = new Dictionary<ITextBoxController, TextBox>();
private static void OnTextBoxControllerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as TextBox;
if (element == null)
throw new ArgumentNullException("d");
var oldController = e.OldValue as ITextBoxController;
if (oldController != null)
{
elements.Remove(oldController);
oldController.SelectAll -= SelectAll;
}
var newController = e.NewValue as ITextBoxController;
if (newController != null)
{
elements.Add(newController, element);
newController.SelectAll += SelectAll;
}
}
private static void SelectAll(ITextBoxController sender)
{
TextBox element;
if (!elements.TryGetValue(sender, out element))
throw new ArgumentException("sender");
element.Focus();
element.SelectAll();
}
}
public interface ITextBoxController
{
event SelectAllEventHandler SelectAll;
}
public delegate void SelectAllEventHandler(ITextBoxController sender);
}
My ViewModel definition:
public class MyViewModel : ITextBoxController
{
public MyViewModel()
{
Value = "My Text";
SelectAllCommand = new RelayCommand(p =>
{
if (SelectAll != null)
SelectAll(this);
});
}
public string Value { get; set; }
public RelayCommand SelectAllCommand { get; private set; }
public event SelectAllEventHandler SelectAll;
}
My View definition:
<Window x:Class="SelectAllSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:SelectAllSample"
Title="Window1" Height="150" Width="150">
<x:Code><![CDATA[
public Window1()
{
InitializeComponent();
DataContext = new MyViewModel();
}
]]></x:Code>
<StackPanel>
<TextBox Text="{Binding Value}" loc:TextBoxAttach.TextBoxController="{Binding}" />
<Button Content="Select All" Command="{Binding SelectAllCommand}" />
</StackPanel>
</Window>
Note: Thanks to Josh Smith for RelayCommand (see code in Figure 3 on this page). It is used in MyViewModel in this example (and just about all my MVVM code).
find a good introduction to attached properties here:
http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx