I'm developing a WPF application and I'm struggling a little bit to understand some of the details of DataContext as it applies to binding. My application uses a business object which is defined like this:
public class MyBusinessObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
// enumerations for some properties
public enum MyEnumValues
{
[Description("New York")]
NewYork,
[Description("Chicago")]
Chicago,
[Description("Los Angeles")]
LosAngeles
}
// an example property
private string _myPropertyName;
public string MyPropertyName
{
get { return _myPropertyName; }
set
{
if (_myPropertyName == value)
{
return;
}
_myPropertyName = value;
OnPropertyChanged(new PropertyChangedEventArgs("MyPropertyName"));
}
}
// another example property
private MyEnumValues _myEnumPropertyName;
public MyEnumValues MyEnumPropertyName
{
get { return _myEnumPropertyName; }
set
{
if (_myEnumPropertyName== value)
{
return;
}
_myEnumPropertyName= value;
OnPropertyChanged(new PropertyChangedEventArgs("MyEnumPropertyName"));
}
}
// example list property of type Widget
public List<Widget> MyWidgets { get; set; }
// constructor
public MyBusinessObject()
{
// initialize list of widgets
MyWidgets = new List<Widget>();
// add 10 widgets to the list
for (int i = 1; i <= 10; i++)
{
MyWidgets.Add(new Widget());
}
// set default settings
this.MyPropertyName = string.empty;
}
}
As you can see, I have some properties that are declared in this class one of which is a list of Widgets. The Widget class itself also implements INotifyPropertyChanged and exposes about 30 properties.
My UI has a combobox which is bound to my list of Widgets like this:
MyBusinessObject myBusinessObject = new MyBusinessObject();
public MainWindow()
{
InitializeComponent();
this.DataContext = myBusinessObject;
selectedWidgetComboBox.ItemsSource = myBusinessObject.MyWidgets;
selectedWidgetComboBox.DisplayMemberPath = "WidgetName";
selectedWidgetComboBox.SelectedValuePath = "WidgetName";
}
The majority of the controls on my UI are used to display the properties of a Widget. When my user selects a Widget from the combobox, I want these controls to display the properties for the selected Widget. I'm currently achieving this behavior by updating my window's DataContext in the SelectionChanged event handler of my combobox like this:
private void selectedWidgetComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.DataContext = selectedWidgetComboBox.SelectedItem;
}
This allows me to bind my controls to the appropriate Widget property like this:
<TextBox Text="{Binding WidgetColor}"></TextBox>
However, not all of the controls in my UI are used to display Widget properties. Some of the controls need to display the properties from MyBusinessObject (for example: MyPropertyName defined above). In this scenario, I can't simply say:
<TextBox Text="{Binding MyPropertyName}"></TextBox>
...because the DataContext of the window is pointing to the selected Widget instead of MyBusinessObject. Can anyone tell me how I set the DataContext for a specific control (in XAML) to reference the fact that MyPropertyName is a property of MyBusinessObject? Thank you!
Instead of changing the DataContext of your window, you should add a property to your MyBusinessObject class like this one:
private Widget _selectedWidget;
public Widget SelectedWidget
{
get { return _selectedWidget; }
set
{
if (_selectedWidget == value)
{
return;
}
_selectedWidget = value;
OnPropertyChanged(new PropertyChangedEventArgs("SelectedWidget"));
}
}
Then bind SelectedWidget to the SelectedItem property of your combobox. Anywhere that you need to use the widget's properties you can do this:
<TextBox Text="{Binding Path=SelectedWidget.WidgetColor}"></TextBox>
try
<TextBox Text="{Binding MyBusinessObject.MyPropertyName}"></TextBox>
this works if MyBusinessObject is the datacontext of the textbox and MyPropertyName is a property of MyBusinessObject
Also, Here is a good article to clarify binding
hope this helps
EDIT 1:
use a relative binding like this:
text="{Binding DataContext.MyPropertyName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TypeOfControl}}}"
So the relatve binding allows you to look up the visual tree to another UI element and use its datacontext. I would consider wrapping your window's contents in a grid. and wet your windows datacontext to the businessobject and the grids datacontext to the widget. That way you can always use the parent window's datacontext through the realtive source binding.
so use the following if your window's datacontext is your business object
text="{Binding DataContext.MyPropertyName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
Related
I have a custom class which a usercontrol has implemented as a dependency property in it's code behind.
public partial class HandControl
{
public HandControl()
{
InitializeComponent();
}
public Seat Seat
{
get
{
return (Seat)GetValue(SeatProperty);
}
set
{
SetValue(SeatProperty, value);
}
}
public static readonly DependencyProperty SeatProperty = DependencyProperty.Register("Seat", typeof(Seat), typeof(HandControl), new PropertyMetadata(null));
}
In my case I've bound the name property in that class to a label inside the usercontrols xaml.
<Label Content="{Binding Seat.Player.Name, RelativeSource={RelativeSource AncestorType={x:Type controls:HandControl}}}"/>
The view model of my window contains the property SeatTl and the xaml is binding to it:
public Seat SeatTr
{
get { return _seatTr; }
private set
{
_seatTr = value;
OnPropertyChanged();
}
}
<customControls:HandControl Grid.Row="1"
Grid.Column="3"
Seat="{Binding SeatTr}" />
However, when I change my class content (the name property) and manually raise OnPropertyChanged in my viewmodel (not the usercontrol), the label is not updated and still has the same content.
private void OnSeatChanged(Player player, SeatPosition seatPosition)
{
//... doing the changes ...\\
OnPropertyChanged("SeatTr");
}
Whats my problem? Anyone got a clue?
I think u should raise OnPropertyChanged for Seat.Player.Name property as It is being chaged.
This cannot be this difficult. The TreeView in WPF doesn't allow you to set the SelectedItem, saying that the property is ReadOnly. I have the TreeView populating, even updating when it's databound collection changes.
I just need to know what item is selected. I am using MVVM, so there is no codebehind or variable to reference the treeview by. This is the only solution I have found, but it is an obvious hack, it creates another element in XAML that uses ElementName binding to set itself to the treeviews selected item, which you must then bind your Viewmodel too. Several other questions are asked about this, but no other working solutions are given.
I have seen this question, but using the answer given gives me compile errors, for some reason I cannot add a reference to the blend sdk System.Windows.Interactivity to my project. It says "unknown error system.windows has not been preloaded" and I haven't yet figured out how to get past that.
For Bonus Points: why the hell did Microsoft make this element's SelectedItem property ReadOnly?
You should not really need to deal with the SelectedItem property directly, bind IsSelected to a property on your viewmodel and keep track of the selected item there.
A sketch:
<TreeView ItemsSource="{Binding TreeData}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
public class TViewModel : INotifyPropertyChanged
{
private static object _selectedItem = null;
// This is public get-only here but you could implement a public setter which
// also selects the item.
// Also this should be moved to an instance property on a VM for the whole tree,
// otherwise there will be conflicts for more than one tree.
public static object SelectedItem
{
get { return _selectedItem; }
private set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnSelectedItemChanged();
}
}
}
static virtual void OnSelectedItemChanged()
{
// Raise event / do other things
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
if (_isSelected)
{
SelectedItem = this;
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
A very unusual but quite effective way to solve this in a MVVM-acceptable way is the following:
Create a visibility-collapsed ContentControl on the same View the TreeView is. Name it appropriately, and bind its Content to some SelectedSomething property in viewmodel. This ContentControl will "hold" the selected object and handle it's binding, OneWayToSource;
Listen to the SelectedItemChanged in TreeView, and add a handler in code-behind to set your ContentControl.Content to the newly selected item.
XAML:
<ContentControl x:Name="SelectedItemHelper" Content="{Binding SelectedObject, Mode=OneWayToSource}" Visibility="Collapsed"/>
<TreeView ItemsSource="{Binding SomeCollection}"
SelectedItemChanged="TreeView_SelectedItemChanged">
Code Behind:
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItemHelper.Content = e.NewValue;
}
ViewModel:
public object SelectedObject // Class is not actually "object"
{
get { return _selected_object; }
set
{
_selected_object = value;
RaisePropertyChanged(() => SelectedObject);
Console.WriteLine(SelectedObject);
}
}
object _selected_object;
You can create an attached property that is bindable and has a getter and setter:
public class TreeViewHelper
{
private static Dictionary<DependencyObject, TreeViewSelectedItemBehavior> behaviors = new Dictionary<DependencyObject, TreeViewSelectedItemBehavior>();
public static object GetSelectedItem(DependencyObject obj)
{
return (object)obj.GetValue(SelectedItemProperty);
}
public static void SetSelectedItem(DependencyObject obj, object value)
{
obj.SetValue(SelectedItemProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, SelectedItemChanged));
private static void SelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is TreeView))
return;
if (!behaviors.ContainsKey(obj))
behaviors.Add(obj, new TreeViewSelectedItemBehavior(obj as TreeView));
TreeViewSelectedItemBehavior view = behaviors[obj];
view.ChangeSelectedItem(e.NewValue);
}
private class TreeViewSelectedItemBehavior
{
TreeView view;
public TreeViewSelectedItemBehavior(TreeView view)
{
this.view = view;
view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue);
}
internal void ChangeSelectedItem(object p)
{
TreeViewItem item = (TreeViewItem)view.ItemContainerGenerator.ContainerFromItem(p);
item.IsSelected = true;
}
}
}
Add the namespace declaration containing that class to your XAML and bind as follows (local is how I named the namespace declaration):
<TreeView ItemsSource="{Binding Path=Root.Children}"
local:TreeViewHelper.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>
Now you can bind the selected item, and also set it in your view model to change it programmatically, should that requirement ever arise. This is, of course, assuming that you implement INotifyPropertyChanged on that particular property.
Use the OneWayToSource binding mode. This doesn't work. See edit.
Edit: Looks like this is a bug or "by design" behavior from Microsoft, according to this question; there are some workarounds posted, though. Do any of those work for your TreeView?
The Microsoft Connect issue: https://connect.microsoft.com/WPF/feedback/details/523865/read-only-dependency-properties-does-not-support-onewaytosource-bindings
Posted by Microsoft on 1/10/2010 at 2:46 PM
We cannot do this in WPF today, for the same reason we cannot support
bindings on properties that are not DependencyProperties. The runtime
per-instance state of a binding is held in a BindingExpression, which
we store in the EffectiveValueTable for the target DependencyObject.
When the target property is not a DP or the DP is read-only, there's
no place to store the BindingExpression.
It's possible we may some day choose to extend binding functionality
to these two scenarios. We get asked about them pretty frequently. In
other words, your request is already on our list of features to
consider in future releases.
Thanks for your feedback.
I decided to use a combination of code behind and viewmodel code. the xaml is like this:
<TreeView
Name="tvCountries"
ItemsSource="{Binding Path=Countries}"
ItemTemplate="{StaticResource ResourceKey=countryTemplate}"
SelectedValuePath="Name"
SelectedItemChanged="tvCountries_SelectedItemChanged">
Code behind
private void tvCountries_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var vm = this.FindResource("vm") as ViewModels.CoiEditorViewModel;
if (vm != null)
{
var treeItem = sender as TreeView;
vm.TreeItemSelected = treeItem.SelectedItem;
}
}
And in the viewmodel there is a TreeItemSelected object which you can then access in the viewmodel.
You can always create a DependencyProperty that uses ICommand and listen to the SelectedItemChanged event on the TreeView. This can be a bit easier than binding IsSelected, but I imagine you will wind up binding IsSelected anyway for other reasons. If you just want to bind on IsSelected you can always have your item send a message whenever IsSelected changes. Then you can listen to those messages anyplace in your program.
Tried may approches to displaying a "no data" if there are no items in listbox. Since I'm on wp7 and using silverlight I can't use DataTriggers, so I've created a control to have it behave consistently across the whole app. BUT I if you set the breakpoint for the set method - it's not being called at all!
The control class
public class EmptyListBox : ListBox
{
public new IEnumerable ItemsSource
{
get
{
return base.ItemsSource;
}
set
{
// never here
base.ItemsSource = value;
ItemsSourceChanged();
}
}
protected virtual void ItemsSourceChanged()
{
bool noItems = Items.Count == 0;
if (noItems)
{
if (Parent is System.Windows.Controls.Panel)
{
var p = Parent as Panel;
TextBlock noData = new TextBlock();
noData.Text = "No data";
noData.HorizontalAlignment = HorizontalAlignment;
noData.Width = Width;
noData.Height = Height;
noData.Margin = Margin;
p.Children.Add(noData);
Visibility = System.Windows.Visibility.Collapsed;
}
}
}
}
This is xaml
<my:EmptyListBox ItemsSource="{Binding Path=MyData}" Name="myListBox">
<my:EmptyListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=name}" />
</DataTemplate>
</my:EmptyListBox.ItemTemplate>
</my:EmptyListBox>
Codebehind:
ClientModel ClientInfo { get; set; }
public ClientView()
{
ClientInfo = new ClientModel();
ClientInfo.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(DataReady);
DataContext = ClientInfo
}
ClientModel class:
public class ClientModel : INotifyPropertyChanged
{
MyData _myData;
public MyData MyData
{
get
{
return _myData;
}
set
{
_myData = value;
NotifyPropertyChanged("MyData");
}
}
public void GetClient(int id)
{
// fetch the network for data
}
}
LINK TO SOLUTION .ZIP THAT SHOWS THE PROBLEM
http://rapidshare.com/files/455900509/WindowsPhoneDataBoundApplication1.zip
Your new ItemSource should be a DependencyProperty.
Anything that is working with Bindings have to be a DependencyProperty.
Simply make it a DependencyProperty.
I think the solution I'd go for is something like this:
Define a new visual state group ItemsStates and two visual states: NoItems and HasItems.
In the ControlTemplate for your custom listbox, add the visual tree for your "no data" state.
In the NoItems state, set the Visibility of your "no data" elements to Visible and set the Visibility of the default ItemsPresenter to Collapsed.
In the HasItems state, swap the Visibility of these elements.
In an OnApplyTemplate override switch to the Empty state by default: VisualStateManager.GoToState(this, "Empty", true);
In an OnItemsChanged override, check whether the items source is empty and use VisualStateManager to switch between these states accordingly.
That should work :)
Create ItemsSource as a DependencyProperty.
Example:
public IEnumerable ItemsSource
{
get { return (IEnumerable)base.GetValue(ItemsSourceProperty); }
set { base.SetValue(ItemsSourceProperty, value); }
}
public static DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
"ItemsSource",
typeof(IEnumerable),
typeof(EmptyListBox),
new PropertyMetadata(null));
try to implement the INotifyPropertyChanged interface and use for ItemsSource an ObservableCollection. In the Setter of your Property just call the OnPropertyChanged method.
Maybe this will help.
Try adding Mode=TwoWay to the ItemsSource binding:
<my:EmptyListBox ItemsSource="{Binding Path=MyData, Mode=TwoWay}" Name="myListBox">
<my:EmptyListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=name}" />
</DataTemplate>
</my:EmptyListBox.ItemTemplate>
</my:EmptyListBox>
I am looking for a way to create an UserControl in silverlight 4 and expose a dependency property, which can accept any type. What I mean by that is, for example, if you look at standard silverlight control like AutoCompleteBox, it is capable of handling any type of collections. So you can bind AutoCompleteBox with IEnumerable<Human> or IENumerable<Animal> etc. And when any item is selected AutoCompleteBox returns the selected value either Human instance or Animal instance via SelectedItem dependency property.
I want to achieve similar flexibility with my usercontrol. I wouild like to expose 2 dependency properties SuggestedItems and SelectedItem. Which ever collection is set to SuggestedItems via consumers of this usercontrol thru Binding, lets take as an example IEnumerable<Car>, the I want SelectedItem property to send instance of Car type back to consumer thru Binding. If I used IEnumerable<Boat>, then I need Boat to be returned with SelectedItem.
I was trying to achieve it by using below example using MVVM, but its not working. I am looking for some clues as to how it should be designed, Am I even on a correct path or I have to completely alter my design?
I created an UserControl called VehicleSelectorUserControl which has its own dedicated ViewModel called VehicleSelectorViewModel with two proerties SuggestedItems, SelectedItem.
And usercontrol has corresponding Dependency properties in its codebehind to expose them to consumers of usercontrol. UserControl XAML has a ListBox which is bound to SuggestedItems property of VehicleSelectorViewModel. When user makes a selection, VehicleSelectorViewModel SelectedItem is set, which them invokes a delegate called ItemSelected to notify VehicleSelectorUserControl codebehind, which then sets the SelectedItem Dependency property to make it available to consumer.
Below is code from the VehicleSelectorUserControl.xaml.cs code behind.
private VehicleSelectorViewModel _TheViewModel;
public UserNameControl()
{
InitializeComponent();
_TheViewModel = Resources["TheViewModel"] as VehicleSelectorViewModel;
_TheViewModel.ItemSelected = OnItemSelected;
}
public IEnumerable<object> SuggestedItems
{
get { return (IEnumerable<object>)GetValue(SuggestedItemsProperty); }
set { SetValue(SuggestedItemsProperty, value); }
}
public static readonly DependencyProperty SuggestedItemsProperty =
DependencyProperty.Register("SuggestedItems", typeof(IEnumerable<object>), typeof(VehicleSelectorControl), new PropertyMetadata(OnSuggestedItemsSet));
private static void OnSuggestedItemsSet(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
_TheViewModel.SuggestedItems = e.NewValue;
}
public object SelectedItem
{
get { return (String) GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(VehicleSelectorControl), null);
private void OnItemSelected()
{
SelectedItem = _TheViewModel.SelectedItem;
}
Its ViewModel VehicleSelectorViewModel code
public Action ItemSelected { get; set; }
private dynamic _SelectedItem;
public dynamic SelectedItem
{
get { return _SelectedItem; }
set
{
if (value != _SelectedItem)
{
_SelectedItem = value;
NotifyPropertyChanged("SelectedItem");
if(ItemSelected != null) ItemSelected.Invoke();
}
}
}
private dynamic _SuggestedItems;
public dynamic SuggestedItems
{
get { return _SuggestedItems; }
set
{
if (value != _SuggestedItems)
{
_SuggestedItems = value;
NotifyPropertyChanged("SuggestedItems");
}
}
}
The XAML of consumer will look like (Consumer has its own ViewModel, which responsible for supplying SuggestedCars [IEnumerable<Car>], SuggestedBoats [IEnumerable<Boat>].
<my:VehicleSelectorControl x:Name="MyCarSelectorControl"
SuggestedItems="{Binding SuggestedCars, Mode=TwoWay}"
SelectedItem="{Binding UserSelectedCar, Mode=TwoWay}" />
<my:VehicleSelectorControl x:Name="MyBoatSelectorControl"
SuggestedItems="{Binding SuggestedBoats, Mode=TwoWay}"
SelectedItem="{Binding UserSelectedBoat, Mode=TwoWay}" />
I have a binding problem in silverlight and I cannot make it work...
I have a control which has a model behind.
The control code behind looks like this
.....
public ClientListingVM()//constructor
{
ClientListingViewModel model = new ClientListingViewModel();//this is my model
DataContext = model;
InitializeComponent();
}
.....
the model code behind looks like this
....
public ObservableCollection<FilterItemDefinition> FilterItemDefinitions
{
get { return _filterItemDefinitions; }
set
{
if (_filterItemDefinitions != value)
{
_filterItemDefinitions = value;
}
OnPropertyChanged("FilterItems");
}
}
....
In the control xaml I have this
....
<my:Filters Height="150" FilterItems="{Binding Path=FilterItemDefinitions}" Columns="{Binding ElementName=ClientGrid, Path=Columns}"/>
....
where Filters is a user control...
Until here everything works ok...
The code behind for filters looks like this
....
public partial class Filters : UserControl
{
public Filters()
{
InitializeComponent();
}
private DependencyProperty FilterItemsProperty = DependencyProperty.Register("FilterItems", typeof(ObservableCollection<FilterItemDefinition>), typeof(Filters), new PropertyMetadata(null, new PropertyChangedCallback(OnChangef)));
public ObservableCollection<FilterItemDefinition> FilterItems
{
get
{
return (ObservableCollection<FilterItemDefinition>)GetValue(FilterItemsProperty);
}
set
{
SetValue(FilterItemsProperty, value);
}
}
private DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(ObservableCollection<DataGridColumn>), typeof(Filters), new PropertyMetadata(null, new PropertyChangedCallback(OnChangec)));
public ObservableCollection<DataGridColumn> Columns
{
get
{
return (ObservableCollection<DataGridColumn>)GetValue(ColumnsProperty);
}
set
{
SetValue(ColumnsProperty, value);
}
}
public static void OnChangef(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public static void OnChangec(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
}
....
and the xaml looks like this
....
<ListBox x:Name="myListBox" ItemsSource="{Binding Path=FilterItems, Mode=TwoWay}" >
<ListBox.ItemTemplate>
<DataTemplate>
<my:FilterItem Columns="{Binding Source={StaticResource DataContextProxy},Path=DataSource.Columns}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
....
So here is the problem:
the ListBox ItemSource is bound to FilterItems... but the Filters DataContext is ClientListingViewModel... and that model does not have FilterItems property.
If in the Filters constructor I set a FiltersViewModel or I say DataContext=this the Filters DataContext will have the property FilterItems and the binding willwork ok.. But the property FilterItemDefinitions from ClientListingViewModel will not get to my control...
I would like that the DataContext for FiltersControl to be his DataContext or the code behind.. but in the same time the binding of his properties with the parent control model to work..
I am sure this is possible and I am doing it in the wrong way.. can someone please help me ...
10x
Leave your existing DataContext alone, you don't want to be doing things like DataContext = this. Instead bind ItemsSource like this:-
ItemsSource="{Binding Path=Parent.FilterItems, ElementName=LayoutRoot}"
This assumes you have default root element named as LayoutRoot. Hence the Parent of this root will be your Filters user control which has a FilterItems property. BTW Two way binding on this property doesn't make a lot of sense.