wpf binding instantiated object to datacontext - wpf

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.

Related

Data Binding doesn't work in xaml

I try to use binding to display Hi in the Text content.
However, when clicking the button, it doesn't work.
Could someone help me to solve the problem?
Thanks.
1.XAML CODE :
<Window x:Class="Wpftest.binding.Window0"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window0" Height="300" Width="300">
<Grid>
<TextBox x:Name="textBox2" VerticalAlignment="Top" Width="168"
Text="{Binding Source= stu, Path= Name, Mode=TwoWay}"/>
</Grid>
</Window>
2.Class :
namespace Wpftest.binding.Model
{
public class student : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name
{
get { return name; }
set { name = value;
if(this.PropertyChanged != null)
{
this.PropertyChanged.Invoke(this, new
PropertyChangedEventArgs("Name"));
}
}
}
}
}
3.XAML.cs:
namespace Wpftest.binding
{
public partial class Window0 : Window
{
student stu;
public Window0()
{
InitializeComponent();
stu = new student();
}
private void button_Click(object sender, RoutedEventArgs e)
{
stu.Name += "Hi!";
}
}
}
There are many ways to achieve what you need; the correct method depends very much on what style of application you want to create. I'll demonstrate two methods that will require minimal changes from your supplied example:
Method 1
Set the DataContext to stu and bind to the Name property.
XAML.cs
private student stu;
public Window0()
{
InitializeComponent();
stu = new student();
DataContext = stu;
}
XAML code
<TextBox Text="{Binding Path=Name, Mode=TwoWay}"/>
Method 2
Generally you will set the DataContext to some object other than the Window (e.g. the ViewModel if you are following the MVVM pattern), but sometimes you may need to bind a control to some property of the Window. In this case the DataContext can't be used, but you can still bind to a property of the Window by using RelativeSource. See below:
XAML.cs
// note this must be a property, not a field
public student stu { get; set; }
public Window0()
{
InitializeComponent();
stu = new student();
}
XAML code
<TextBox Text="{Binding Path=stu.Name, Mode=TwoWay,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
Hint: if you are having trouble with WPF data binding, then it often helps to look at the debugger output window to see the binding trace messages. And debugging can be further enhanced by adding this namespace to the Window element
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
and then setting the TraceLevel e.g.
<TextBox Text="{Binding Source=stu, diag:PresentationTraceSources.TraceLevel=High}"/>
Basically you need to set DataContext property to your Window.
For example:
public MainWindow()
{
DataContext=new YourViewModel();
}
DataContext of Window is a way to communicate between View(XAML) and ViewModel(C# code)
In addition, you can add DataContext in xaml:
<Window.DataContext>
<local:YourViewModel/>
</Window.DataContext>
Also, instead of handling Click event, you should use Command property of Button. Example can be seen here.

Display Hierarchical Data in TreeView

I am simply trying to display hierarchical Data in a TreeView, but I just can't figure out how to make it display more than the First two Levels. (And i have read almost evry TreeView post, maybe the problem is my (miss)understatement of the Bindings in this case)
I have simplyfied my Datastructure for this test:
public class Node
{
public List<Node> Children { get; set; }
public Node Parent { get; set; }
public string Expression { get; set; }
}
Xaml currently looks like this: (Please notice that I have changed it several times now, but this here is the original state i have come up with: )
<Window x:Class="Klammern_Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Klammern_Test"
Title="MainWindow" Height="439" Width="402">
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Node}">
<TreeViewItem ItemsSource="{Binding Children}" Header="{Binding Expression}"/>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding Root}" Margin="12,41,12,12" Name="treeView" />
</Grid>
</Window>
And this is how I am trying to Populate my Treeview:
public partial class MainWindow : Window
{
public Node Root { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void StartButton_Click(object sender, RoutedEventArgs e)
{
Parser = new StringParser();
Root = Parser.Parse(Tbx_Eingabe.Text);
treeView.Items.Add(PopulateTreeView(Root));
}
private TreeViewItem PopulateTreeView(Node node)
{
TreeViewItem treeViewItem = new TreeViewItem();
treeViewItem.IsExpanded = true;
treeViewItem.Header = node.Expression;
foreach (Node child in node.Children)
{
treeViewItem.Items.Add(new TreeViewItem() { Header = child.Expression });
if (child.Children.Count > 0)
{
PopulateTreeView(child);
}
}
return treeViewItem;
}
}
What am I missing?
EDIT:
After trying around with almulo's hints, I found this with the Snoop-tool, but I can't tell what it means at all, I have found no other red line and no entry to the Binding Errors column at all.
Using a TreeViewItem inside a HierarchicalDataTemplate which is what your TreeView's TreeViewItems will use to create themselves is... confusing.
Inside your HierarchicalDataTemplate you should just add the controls you want the item "header" to have. In this case, I guess it should be a TextBlock since you just wanna show some text.
Then use the HierarchicalDataTemplate.ItemSource property to bind the children of your node.
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Node}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Expression}" />
</HierarchicalDataTemplate>
</Window.Resources>
Also, in you code-behind, you shouldn't manipulate the TreeView.Items or the TreeViewItem.Items directly, since you are already using Bindings and the ItemsSource properties.
Instead, remove the PopulateTreeView method and let your Root property work as items source for the TreeView. But in order for this to work, you'll have to notify the view when the Root property changes its value.
To do so, implement the INotifyPropertyChanged interface and fire the PropertyChanged event every time Root changes.
EDIT: The ItemsControl property ItemsSource expects a collection (more specifically, an IEnumerable), so Root needs to be one. Even if it has a single item, like this:
public class MainWindow : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// ...
private void StartButton_Click(object sender, RoutedEventArgs e)
{
Parser = new StringParser();
Root = new Node[] { Parser.Parse(Tbx_Eingabe.Text) };
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Root"));
}
// ...
}

MVVM DataTemplate Binding Issue

Perhaps this is a case of too much cold medicine, but I just can't seem to get this Binding correct.
Here is the (simplified) Window, with the a DataTemplate for each ViewModel type, which should just show an associated View:
<Window ...>
<Window.Resources>
<DataTemplate DataType="{x:Type local:DefaultViewViewModel">
<local:DefaultView />
</DataTemplate>
<DataTemplate DataType="{x:Type other:AnotherViewModel">
<other:AnotherView />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding CurrentViewModel}" />
</Grid>
</Window>
Here is some of the MainViewModel (the actual ShowABCView methods are Command functions that do more than is shown here, for brevity):
class MainViewModel : ViewModelBase
{
private Stack<ViewModelBase> mContentViewStack;
public MainViewModel()
{
mContentViewStack = new Stack<ViewModelBase>();
ShowDefaultView();
}
public ViewModelBase CurrentViewModel
{
get { return mContentViewStack.Peek(); }
}
private ShowDefaultView()
{
DefaultViewViewModel viewModel = new DefaultViewViewModel();
mContentViewStack.Push(viewModel);
NotifyPropertyChanged("CurrentViewModel");
}
private ShowAnotherView()
{
AnotherViewModel viewModel = new AnotherViewModel();
mContentViewStack.Push(viewModel);
NotifyPropertyChanged("CurrentViewModel");
}
}
And the MainWindow startup code:
public MainWindow()
{
this.DataContext = new MainViewModel();
}
When I run this, I get the error
System.Windows.Data.Error: 40:
BindingExpression path error:
'Content' property not found on
'object' 'DefaultViewViewModel'
I know I'm missing something obvious here, but the Nyquil and friends betray me...
*EDIT - DefaultViewViewModel and DefaultView *
DefaultViewViewModel:
// ViewModelBase is basically just a wrapper for INotifyPropertyChanged,
// plus some other common-to-my-project properties
// (NOT INCLUDING A Content PROPERTY)
class DefaultViewViewModel : ViewModelBase
{
public DefaultViewViewModel() : base()
{
}
}
DefaultView:
<UserControl ...>
<TextBlock Text="Some Hard Coded Text Formatted To My Liking" />
</UserControl>
Well you haven't shown the code for the DefaultViewViewModel yet
but my guess is you defined "Content" as a field and not as a property.
to make sure that it will fix it, go ahead and overkill it by making Content a dependency property
hope that helps
Found the answer upstream from where I was looking. There was an incorrect binding (used regular Binding without RelativeSource of the TemplatedParent) in the base View control that all of our Views use.
No more Nyquil for me...

WPF: How to bind to a nested property?

I can bind to a property, but not a property within another property. Why not? e.g.
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}"...>
...
<!--Doesn't work-->
<TextBox Text="{Binding Path=ParentProperty.ChildProperty,Mode=TwoWay}"
Width="30"/>
(Note: I'm not trying to do master-details or anything. Both properties are standard CLR properties.)
Update: the problem was that my ParentProperty depended on an object in XAML being initialized. Unfortunately that object was defined later in the XAML file than the Binding, so the object was null at the time when my ParentProperty was read by the Binding. Since rearranging the XAML file would screw up the layout, the only solution I could think of was to define the Binding in code-behind:
<TextBox x:Name="txt" Width="30"/>
// after calling InitializeComponent()
txt.SetBinding(TextBox.TextProperty, "ParentProperty.ChildProperty");
You can also set DataContext for TextBox in XAML (I don't know if it's optimal solution, but at least you don't have to do anything manually in codeBehind except of implementing INotifyPropertyChanged). When your TextBox has already DataContext (inherited DataContext) you write code like this:
<TextBox
DataContext="{Binding Path=ParentProperty}"
Text="{Binding Path=ChildProperty, Mode=TwoWay}"
Width="30"/>
Be aware that until your DataContext for TextBox isn't ready binding for Text property will not be 'established' - you can add FallbackValue='error' as Binding parameter - it will be something like indicator which will show you if binding is OK or not.
All I can think of is that the ParentProperty is being changed after the Binding is created, and it does not support change notification. Every property in the chain must support change notification, whether it be by virtue of being a DependencyProperty, or by implementing INotifyPropertyChanged.
Do both the ParentProperty and your class implement INotifyPropertyChanged?
public class ParentProperty : INotifyPropertyChanged
{
private string m_ChildProperty;
public string ChildProperty
{
get
{
return this.m_ChildProperty;
}
set
{
if (value != this.m_ChildProperty)
{
this.m_ChildProperty = value;
NotifyPropertyChanged("ChildProperty");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}
public partial class TestClass : INotifyPropertyChanged
{
private ParentProperty m_ParentProperty;
public ParentProperty ParentProperty
{
get
{
return this.m_ParentProperty;
}
set
{
if (value != this.m_ParentProperty)
{
this.m_ParentProperty = value;
NotifyPropertyChanged("ParentProperty");
}
}
}
}
public TestClass()
{
InitializeComponent();
DataContext = this;
ParentProperty = new ParentProperty();
ParentProperty.ChildProperty = new ChildProperty();
#region INotifyPropertyChanged Members
#endregion
}

Setting Custom Properties in UserControl via DataBinding

Say I have a very simple UserControl that - for all intents and purposes - is nothing more than TextBox:
public partial class FooBox : UserControl
{
public static readonly DependencyProperty FooTextProperty =
DependencyProperty.Register("FooText", typeof(string), typeof(FooBox));
public FooBox()
{
InitializeComponent();
}
public string FooText
{
get { return textBlock.Text; }
set { textBlock.Text = value; }
}
}
<UserControl x:Class="Namespace.FooBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid>
<TextBlock x:Name="textBlock" />
</Grid>
</UserControl>
On the form it's declared as:
<local:FooBox FooText="{Binding Name}" />
The form's DataContext is set to an object that has a Name property. But this is not working for me. What am I missing?
The "get" and "set" parts of a property declaration in a DependencyProperty aren't actually called by the databinding system of WPF - they're there essentially to satisfy the compiler only.
Instead, change your property declaration to look like this:
public string FooText
{
get { return (string)GetValue(FooTextProperty); }
set { SetValue(FooTextProperty, value); }
}
... and your XAML to:
<UserControl ...
x:Name="me">
<Grid>
<TextBlock Text="{Binding FooText,ElementName=me}" />
</Grid>
</UserControl>
Now your TextBox.Text simply binds directly to the "FooText" property, so you can in turn bind the FooText property to "Name" just like you're currently doing.
Another way is to bind TextBlock.Text to a RelativeSource binding that finds the FooText property on the first ancestor of type "FooBox", but I've found that this is more complex than just giving the control an internal x:Name and using element binding.
Turns out the real problem is I was expecting the WPF framework to set my public property whereupon my code would respond to the changes and render according to the new value. Not so. What WPF does is call SetValue() directly and completely circumvents the public property. What I had to do was receive property change notifications using DependencyPropertyDescriptor.AddValueChanged and respond to that. It looks something like (inside the ctor):
var dpd = DependencyPropertyDescriptor
.FromProperty(MyDependencyProperty, typeof(MyClass));
dpd.AddValueChanged(this, (sender, args) =>
{
// Do my updating.
});

Resources