I need to bind something to a child of an element in my VisualTree .
in a UserControl:
<StackPanel>
<DataGrid x:Name="dataGrid" />
<Control Tag="{Binding ElementName=dataGrid}" />
</StackPanel>
in DataGrid's Template :
<Template TargetType=DataGrid>
......
<Control x:Name="FindMe" />
......
</Template>
What i thought of doing is traversing the VisualTree of the DataGrid , for this purpose iv'e created a
custom markup extension :
public class TemplatePartBinding : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
Binding binding = new Binding();
binding.ElementName = ElementName;
// HOW DO I GET THE SOURCE OBJECT FROM THE BINDING ?
DataGrid dataGrid = // Extract the DataGrid from the binding.
Control _findMe = VisualTreeHelperExtentions.FindVisualChild<Control>(dataGrid,"FindMe");
binding.Target = _findMe;
binding.Path = new PropertyPath("Tag");
return binding;
}
[ConstructorArgument("ElementName")]
public string ElementName
{
get;
set;
}
[ConstructorArgument("TemplatePartName")]
public string TemplatePartName
{
get;
set;
}
}
Here in ProvideValue i wan't to locate the DataGrid (Source Object for the binding ) after giving the binding's ElementName value it's name ,
How do i extract the DependencyObject (My DataGrid) from the binding iv'e just created ?
You can get the DataGrid instance in markup extension provide value method but FindMe Control you won't be able to get with VisualTree extension methods because when this method gets called, Visual Tree for dataGrid is not created at that time.
Morever, logical tree won't be of any help either since Control is Visual child and not logical child of dataGrid.
However, for your question to find dataGrid, you can get like this:
public override object ProvideValue(IServiceProvider serviceProvider)
{
IRootObjectProvider provider = (IRootObjectProvider)serviceProvider
.GetService(typeof(IRootObjectProvider));
DataGrid dataGrid =
LogicalTreeHelper.FindLogicalNode((DependencyObject)provider.RootObject,
ElementName) as DataGrid;
....
}
IRootObjectProvider will get you RootObject which will be UserControl and eventually can get you DataGrid by traversing LogicalTree and not VisualTree since it will return null. (Visual Tree not created yet).
Related
I have a Model with INotifyPropertyChanged handling copied from tutorials:
public event PropertyChangedEventHandler? PropertyChanged;
protected void Notify(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
When I update a member of the class, I call the handler:
public string? Id
{
get => _id;
set
{
if (value != _id)
{
_id = value;
Notify(nameof(Id));
}
}
}
And in the view code behind I have:
private Goal _goal;
public GoalControl()
{
InitializeComponent();
this._goal = new MyGoal();
this.DataContext = _goal;
Binding binding = new Binding("Text");
binding.Source = _goal.Id;
binding.Mode = BindingMode.TwoWay;
_ = Id.SetBinding(TextBox.TextProperty, binding);
}
But the view doesn't pick up any changes to the field. When I debug, I find that PropertyChanged is always null. How should I set it to a useful value?
This is in a user control, by the way, which will be generated dynamically so I don't think I can do the binding from XAML.
Assuming that Id is a TextBox in your GoalControl, you would bind its Text property to the Id of the MyGoal object in the DataContext like shown below.
You do not set the Source property of the Binding, because the source object should be provided by the current DataContext. Also note that TwoWay is the default binding mode for the TextBox.Text property and does not need to be set explicitly.
public GoalControl()
{
InitializeComponent();
_goal = new MyGoal();
DataContext = _goal;
Binding binding = new Binding("Id");
Id.SetBinding(TextBox.TextProperty, binding);
}
The Binding could as well be written in XAML:
<TextBox Text="{Binding Id}"/>
Since this is in a UserControl, you should however not set the DataContext at all. UserControls, as any other controls, should not have "private" view models like your MyGoal object.
The UserControl would instead expose a dependency property Id, which is bound when you use the control, like
<local:GoalControl Id="{Binding SomeViewModelId}"/>
In the UserControl's XAML, the Binding to the own property would specify the Source object as RelativeSource:
<TextBox Text="{Binding Id,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
EDIT : Question was not clear enough. In fact there are two of them.
Q1 :
I have a UserControl "CustomView" that is dynamically created with a template:
<Window.Resources>
<DataTemplate DataType="{x:Type my:CustomViewModel}">
<my:CustomView/>
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding Path=CustomList}"/>
Where CustomList is a Property of type ObservableCollection<'CustomViewModel> belonging to MainWindowViewModel, which is the Window's DataContext.
In CustomView's Xaml code, there are some Properties binded to CustomViewModel's Properties. Everything works properly. But when I try to do this in CustomView's code behind :
public CustomView()
{
InitializeComponents();
if (this.DataContext == null) Console.WriteLine ("DataContext is null");
else Console.WriteLine(this.DataContext.GetType().ToString());
}
It is written in Console : 'DataContext is null', even if bindings are working betweeen CustomView and CustomViewModel. Do you know why it's working?
Q2 :
Now, imagine that CustomView has another UserControl (IndexPicker) inside of it. IndexPicker has an associated ViewModel too (IndexPickerViewModel) who's in charge with data access. I need to bind one property ("Index") of this IndexPickerViewModel to the previous CustomViewModel's property "Id". I want to instantiate it in StaticResources and bind it to the CustomViewModel (which I believe is the dataContext according to my previous question):
<UserControl x:Class="MyView.CustomView"
...
<UserControl.Resources>
<DataTemplate DataType="{x:Type myPicker:IndexPickerViewModel}">
<myPicker:IndexPicker/>
</DataTemplate>
<myPicker:IndexPickerViewModel x:Key="pickerViewModel" Index="{Binding Path=Id}/>
</Window.Resources/>
<ContentControl Content={StaticResource pickerViewModel}/>
What I have tried : I tried to make "IndexPickerViewModel" inherit from "DependencyObject" and make "Index" a DependencyProperty. But the following error message shows up :
"System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Id; DataItem=null; target element is 'IndexPickerViewModel' (HashCode=59604175); target property is 'Index' (type 'Nullable`1')
I believe this is because of what I asked just above. But is it possible to do something like that? If yes, what am I missing? And : Is this a stupid idea?
Thank you in advance for any help.
Now, imagine that CustomView has another UserControl (IndexPicker) inside of it. IndexPicker has an associated ViewModel too (IndexPickerViewModel) who's in charge with data access. I need to bind one property ("Index") of this IndexPickerViewModel to the previous CustomViewModel's property "Id". I want to instantiate it in StaticResources and bind it to the CustomViewModel (which I believe is the dataContext according to my previous question)
If IndexPicker doesn't have an explicitly set datacontext then IndexPicker will inherit the datacontext from it's parent element.
However if IndexPicker does already have a datacontext then you will have to use relative source binding with an ancestor search:
Index="{Binding Id, RelaticeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, FallbackValue={x:Null}}"
Of course you can probably already sense that this is messy. Going after standard properties of a UIElement or Control is quite safe (and common), but when you start going after custom properties then you are introducing dependencies between the child control and its parent (when the child control shouldn't know much of anything about its parent), and you are also bound to start getting binding errors at some stage (hence the use of a fallback value).
It seems that I've asked too early because I've found answers by myself.
Answer to Question1
When you have a UserControl that is dynamically created from a DataTemplate in which it is associated with another object (belonging to a ViewModel or to a Resource), this object is defined as the DataContext of the UserControl. However, you cannot reach it in the UserControl's constructor, you have to wait until the "Loaded" event is raised :
public CustomUserControl()
{
InitializeComponent();
Console.WriteLine(this.DataContext.ToString());
// This doesn't work : DataContext is null
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
Console.WriteLine(this.DataContext.ToString());
// or
Console.WriteLine((sender as UserControl).DataContext.ToString());
// this is Ok.
}
Answer to Question2
This is how you do to get a UserControl whose ViewModel is instantiated in a parent UserControl.Resources :
You don't do it.
Instead, you instantiate its ViewModel in its parent ViewModel. Full example :
MainWindow.xaml:
<Window x:Class="MainWindow"
...
xmlns:local="clr-namespace:my_project_namespace"
xmlns:cust="clr-namespace:CustomUserControl;assembly=CustomUserControl"
...>
<Window.Resources>
<DataTemplate DataType="{x:Type cust:CustomControlViewModel}">
<cust:CustomControlView>
</DataTemplate>
<!-- Here are listed all the types inheriting from CustomControlViewModel and CustomControlView.-->
<!-- CustomControlViewModel and CustomControlView are used as "abstract" classes-->
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel>
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding Path=CustomVMList}"/>
</Grid>
</Window>
MainWindowViewModel.cs:
namespace my_project_namespace
{
public class MainWindowViewModel
{
public ObservableCollection<CustomControlViewModel> CustomVMList { get; set; }
public MainWindowViewModel()
{
CustomVMList = new ObservableCollection<CustomControlViewModel>();
// Fill in the list...
}
}
}
CustomControlView.xaml
<UserControl x:class="CustomUserControl.CustomControlView"
...
xmlns:my="clr-namespace:IndexPicker;assembly=IndexPicker"
...>
<UserControl.Resources>
<DataTemplate DataType="{x:Type my:IndexPickerViewModel}">
<my:IndexPickerView/>
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}/>
<ContentControl Content="{Binding Path=MyIndexPicker}"/>
</Grid>
</UserControl>
And this is where it's interesting :
CustomControlViewModel.cs:
namespace CustomUserControl
{
public class CustomControlViewModel : INotifyPropertyChanged
{
public IndexPickerViewModel MyIndexPicker{ get; set; }
public string Name { get ; set; }
public int Id
{
get
{
return MyIndexPicker.Index;
}
set
{
if (value != MyIndexPicker.Index)
{
MyIndexPicker.Index = value;
NotifyPropertyChanged("Id");
}
}
}
public CustomControlViewModel(string _name)
{
Name = _name;
MyIndexPicker = new IndexPickerViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}
}
IndexPickerView.xaml:
<UserControl x:Class="IndexPicker.IndexPickerView"
...
...>
<Grid>
<Combobox ItemsSource="{Binding Path=MyTable}"
DisplayMemberPath="ColumnXYZ"
SelectedItem={Binding Path=SelectedRow}/>
</Grid>
</UserControl>
Finally
IndexPickerViewModel.cs:
namespace IndexPicker
{
public class IndexPickerViewModel : INotifyPropertyChanged
{
private DataAccess data;
public DataView MyTable { get; set; }
private DataRowView selectedRow;
public DataRowView SelectedRow
{
get { return selectedRow; }
set
{
selectedRow = value;
NotifyPropertyChanged("SelectedRow");
}
}
public int? Index
{
get
{
if (SelectedRow != null) return (int?)selectedRow.Row["Column_Id"];
else return null;
}
set
{
SelectedRow = MyTable[MyTable.Find((int)value)];
NotifyPropertyChanged("Index");
}
}
public IndexPickerViewModel()
{
data = new DataAccess();
MyTable = data.GetTableView("tableName");
MyTable.Sort = "Column_Id";
}
// And don't forget INotifyPropertyChanged implementation
}
}
This configuration is used with several different UserControls inheriting from CustomControlView and their ViewModel inheriting from CustomControlViewModel. They are dynamically created and listed in CustomVMList. Here CustomControlViewModel containing an IndexPicker is already a specialization.
Concrete use: Generic Dialog for CRUD database Tables, which can dynamically create UserControls depending on each Table Columns. The specialization shown here is used in case of a column containing a foreign key.
I hope its clear.
The code listed above may contain mistakes. Criticisms and remarks are welcome.
I'm trying to learn about DependencyProperty. To do so I want to create a new UserControl which displays a list.
The location of this list must exist in the parent as a property. For this, I only have MainWindow, MainWindowViewModel (these are the parent) and the UserControl (the child) (which is currently using code behind).
In my MainWindow I have
<Grid>
<uc:RecentList MessageList="{Binding Messages}" />
</Grid>
And in the code behind
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
And the ViewModel
public MainWindowViewModel()
{
this.Messages = new ObservableCollection<string>();
this.Messages.Add("Item 1");
this.Messages.Add("Item 2");
this.T = "hi";
}
public ObservableCollection<string> Messages { get; set; }
In the UserControl I have
<Grid>
<ListView ItemsSource="{Binding MessageList}"></ListView>
<TextBlock Text="I'm such text to verify this control is showing" />
</Grid>
And the code behind is
public static readonly DependencyProperty MessageListProperty =
DependencyProperty.Register(
"MessageList", typeof(IEnumerable<string>), typeof(RecentList));
public IEnumerable<string> MessageList
{
get { return (IEnumerable<string>)GetValue(MessageListProperty); }
set { SetValue(MessageListProperty, value); }
}
The issue I have is the binding is not working. I can see this in the Output Window, with the Error:
Error 40 : BindingExpression path error: 'MessageList' property not found on 'object' ''MainWindowViewModel' (HashCode=26034861)'. BindingExpression:Path=MessageList; DataItem='MainWindowViewModel' (HashCode=26034861); target element is 'ListView' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
I understand the issue but I am confused by it. It's looking in the right place (in the MainWindowViewModel) but it is looking I don't understand why the UserControl is looking for the MessageList in the MainWindowViewModel. I guess it's because that is where I set the datacontext but, I also thought it that if I added this.DataContext = this; to the UserControl's constructor then it's wrong (I've tried it, it didn't work either).
Updating my UserControl to
<ListView ItemsSource="{Binding MessageList, RelativeSource={RelativeSource Mode=TemplatedParent}}"></ListView>
Helps in the sense I don't get the error message, but I also don't see the result.
This is what I think is happening when the application loads:
MainWindow loads
MainWindow then see's the UserControl and notes it requires a property.
Before WPF calls the UserControl constructor, it grabs the value of the property. It then initializes the component and automatically pushes the value to the UserControl's property
How can my UserControl use the Parents (MainWindow) property (Messages)
The Binding in the UserControl's XAML should have the UserControl instance as its source object, e.g. like this:
<ListView ItemsSource="{Binding MessageList,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Alternatively you could set x:Name on the UserControl and use an ElementName binding:
<UserControl ... x:Name="self">
...
<ListView ItemsSource="{Binding MessageList, ElementName=self}" />
...
</UserControl>
Besides that you should usually not set the DataContext of the UserControl to itself (like DataContext = this;) because that would effectively prevent inheriting the DataContext from the UserControl's parent element, which is necessary for an "external" binding to work, like:
<uc:RecentList MessageList="{Binding Messages}" />
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.
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)