Passing WPF user control selection to host control - wpf

I have a WPF user control with a list box. I want to pass the selected item in the list box to the calling control through binding. How can I achieve this?

You can expose a new property for SelectedItem on your user control and bind it to the child control ListBox.
Code for your user control (I inherited from Control though):
public class CustomListControl : Control
{
static CustomListControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListControl), new FrameworkPropertyMetadata(typeof(CustomListControl)));
SelectedItemProperty = ListBox.SelectedItemProperty.AddOwner(typeof(CustomListControl));
}
public static readonly DependencyProperty SelectedItemProperty;
public Object SelectedItem
{
get { return this.GetValue(SelectedItemProperty); }
set { this.SetValue(SelectedItemProperty, value); }
}
}
And add the binding from the inner ListBox to your UserControl in the Generic.xaml markup:
<ListBox
SelectedItem="{Binding RelativeSource={RelativeSource AncestorLevel=1, AncestorType={x:Type local:CustomListControl},Mode=FindAncestor},Path=SelectedItem, Mode=TwoWay}"
</ListBox>

Related

Databinding to ListView Item in DataTemplate

I have a user control (WaypointInfoControl) that I wrote that has a dependency property named TheGraphic as shown here:
public Graphic TheGraphic
{
get { return (Graphic)GetValue(TheGraphicProperty); }
set { SetValue(TheGraphicProperty, value); }
}
public static readonly DependencyProperty TheGraphicProperty =
DependencyProperty.Register("TheGraphic", typeof(Graphic), typeof(WaypointInfoControl), new PropertyMetadata(default(Graphic)));
I have a viewmodel that has a Waypoints property defined like this:
private ObservableCollection<Graphic>_Waypoints = new GraphicCollection();
public ObservableCollection<Graphic> Waypoints
{
get { return _Waypoints; }
set { RaiseAndSetIfChanged(ref _Waypoints, value); }
}
In my xaml, I have a ListView that I want to populate with Waypoints:
<ListView ItemsSource="{Binding Waypoints}" >
<ListView.ItemTemplate >
<DataTemplate >
<controld:WaypointInfoControl TheGraphic="????" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
How do I bind TheGraphic to the individual item in the ListView that it represents?
Your ItemsSource is bound to a collection of Graphic objects which means that the DataContext for each item in your ListView will be a single Graphic object. Since the DependencyProperty that you are looking to bind to is looking for the Graphic object you will just want to bind to the entire DataContext, you achieve this by using the binding markup extension without specifying a path (this just causes the binding to pull in the entire DataContext which in your case is the Graphic object that you are looking for).
So this should work:
<ListView ItemsSource="{Binding Waypoints}" >
<ListView.ItemTemplate >
<DataTemplate >
<controld:WaypointInfoControl TheGraphic="{Binding}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

WPF user control with View Model

I created a my own user control wrappping a GridControl and defined some columns, so I can reuse this control in multiple places. I created a dependency property called AttachmentList in my user control to hold a list of strings I want to display in the grid.
<dxg:GridControl AutoGenerateColumns="None" EnableSmartColumnsGeneration="True"
ItemsSource="{Binding Path=AttachmentList, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, UpdateSourceTrigger=PropertyChanged}"/>
I my parent window, I use
<vm:FileListGridControl AllowDrop="True"
AttachmentList="{Binding Path=Email.FileList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<dxmvvm:Interaction.Behaviors>
<vm:GridDragDropBehavior/>
</dxmvvm:Interaction.Behaviors>
</vm:FileListGridControl>
Email.FileList is defined as ObservableCollection in my ViewModel. GridDragDropBehavior will call Email.FileList.Add() to add strings to Email.FileList. Problem is I can see strings have been added to FileList, but not shown in the GridControl.
I though use ObserableCollection and twoway binding should update the UI. What did I do wrong?
Edit Here are the defintions
public static readonly DependencyProperty AttachmentListProperty = DependencyProperty.Register("AttachmentList", typeof(IList<FileWrapper>), typeof(FileListGridControl));
public IList<FileWrapper> AttachmentList
{
get { return (IList<FileWrapper>)GetValue(AttachmentListProperty); }
set { SetValue(AttachmentListProperty, value); }
}
In My Email class, I defined
ObservableCollection<FileWrapper> _FileList = new ObservableCollection<FileWrapper>();
public ObservableCollection<FileWrapper> FileList { get { return _FileList; } set { _FileList = value; } }
If I remove my control and add the GridControl directly in the Parent window, everything works as expected.

Why WPF uses control's view model when binding value to the control?

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>

WPF ObservableCollection in xaml

I have created an ObservableCollection in the code behind of a user control. It is created when the window loads:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
Entities db = new Entities();
ObservableCollection<Image> _imageCollection =
new ObservableCollection<Image>();
IEnumerable<library> libraryQuery =
from c in db.ElectricalLibraries
select c;
foreach (ElectricalLibrary c in libraryQuery)
{
Image finalImage = new Image();
finalImage.Width = 80;
BitmapImage logo = new BitmapImage();
logo.BeginInit();
logo.UriSource = new Uri(c.url);
logo.EndInit();
finalImage.Source = logo;
_imageCollection.Add(finalImage);
}
}
I need to get the ObservableCollection of images which are created based on the url saved in a database. But I need a ListView or other ItemsControl to bind to it in XAML file like this:
But I can't figure it out how to pass the ObservableCollection to the ItemsSource of that control. I tried to create a class and then create an instance of a class in xaml file but it did not work. Should I create a static resource somehow>
Any help will be greatly appreciated.
Firstly, the ObservableCollection is a local variable. What you need to do is have it as a private global variable and expose it with a public property. You can use the INotifyPropertyChanged interface to have the image data update automagically when the actual collection itself changes.
In your XAML, you then need to set the DataContext to self, and you can then directly bind your public property to the ItemsSource. You may want to use an ItemTemplate for displaying the items in a custom manner.
Cheers,
Adam
Example as requested:
In C#:
public MyWindowClass
{
public ObservableCollection<image> MyImageCollection
{
get;
set;
}
}
In XAML:
<UserControl
...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
...
<ListBox ItemsSource="{Binding MyImageCollection}" ItemTemplate="*yourtemplateresource*" />
...
</UserControl>
Now, the reason that I mentioned using INotifyPropertyChanged is that if you try:
MyImageCollection = new ObservableCollection<image>();
The items in the listbox will not automatically update. With an ObservableCollection, however, you do not need to implement INotifyPropertyChanged for basic addition and removal of list items.
You have to set the DataContext of the UserControl to your collection:
DataContext = _imageCollection
You can do that in the UserControl_Loaded() method.
Next you need to bind the ItemsSource of the ListView in the XAML:
<ListView ItemsSource="{Binding}"/>
The {Binding} is equivalent to {Binding .} which binds to the DataContext of the UserControl. If you need "more stuff" in your DataContext you can instead create a class like this:
class ViewModel : INotifyPropertyChanged {
public ObservableCollection Images { get { ... } }
...
}
Use this class for the DataContext:
DataContext = new ViewModel();
And replace the binding to bind to the Images property:
<ListView ItemsSource="{Binding Images}"/>
Then you can add another property to ViewModel:
class ViewModel : INotifyPropertyChanged {
public ObservableCollection Images { get { ... } }
public String Message { get { ... } set { ... } }
...
}
And bind it to a control:
<TextBlock Text="{Binding Message}"/>
Remember to fire the PropertyChanged event when the Message property is changed in ViewModel. This will update the UI when view-model properties are changed by code.

CheckedItems property for custom CheckBoxList control in Silverlight

I need to implement CheckBoxList control with ItemsSource and CheckedItems properties. Items from ItemsSource should be displayed as checked checkboxes if CheckedItems contains these values or unchecked otherwise. Also I need two-way databinding support for CheckedItems property (value of this property should be updated when user clicks on checkboxes).
Here some code which probably can help to understand my problem
XAML:
<UserControl x:Class="Namespace.Controls.CheckBoxList" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ListBox x:Name="LayoutRoot">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
Code behind:
public partial class CheckBoxList : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CheckBoxList), null);
public static readonly DependencyProperty CheckedItemsProperty = DependencyProperty.Register("CheckedItems", typeof(IEnumerable), typeof(CheckBoxList), null);
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public IEnumerable CheckedItems
{
get { return (IEnumerable)GetValue(CheckedItemsProperty); }
set { SetValue(CheckedItemsProperty, value); }
}
public CheckBoxList()
{
InitializeComponent();
LayoutRoot.SetBinding(ItemsControl.ItemsSourceProperty, new Binding("ItemsSource") { Source = this });
}
}
I think that I need to bind ListBox to UserControl with custom converter, which will return collection of items with additional IsChecked property, but it works only in case of one-way data binding.
Looks like I need two-way binding to two properties at one time, but I don't know how to implement it and will appreciate any help with this issue.
Thanks in advance.
First of all you should consider deriving from ListBox rather than UserControl. The ListBox already does most of what you want.
Secondly consider one way binding to an IList. You can then add and remove entires to that IList as the respective items are selected.
Rather than try to bind a CheckBox control in an Item Template you make a copy of the ListBox styles, place them in Generic.xaml as the style of your new control. Then modify the unselected and selected visual states using a checked and unchecked check box as part of the visual appearance.
Now you can attach to the SelectionChanged event and use the Event args AddedItems list to add to the bound IList and the RemovedItems list to remove items from the bound list.
You would need to clear and re-add the set of items to the list box SelectedItems list when either your CheckedItems is assigned or the ItemsSource is changed.
There are probably a number gotchas that you will need to work round but this seems like a more direct path to your goal than starting from scratch with a UserControl base.
Add an observable collection for your list box datasource to your datacontext:
private ObservableCollection<MyItem> _myItems;
public ObservableCollection<MyItem> MyItems
{
get { return _searchByFields; }
set
{
_myItems = value;
}
}
Add a class to hold the data about your checkboxes:
public class MyItem
{
public bool Checked {get; set; }
public string MyItemValue { set ; set; }
}
Then in your data template bind listbox to the collection and your data template checkboxes to the respective MyItem properties:
<UserControl x:Class="Namespace.Controls.CheckBoxList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ListBox x:Name="LayoutRoot"
DataContext="[Dataconext here]"
ItemsSource={Binding MyItems}>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Checked, Mode=TwoWay}"
Content="{Binding MyItemValue}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
Don't forget to set the DataContext of the binding to the appropriate class (you might be doing this in the XAML or the code behind perhaps)

Resources