WPF Binding source is not DataContext as expected - wpf

I have a simple binding question as I feel I'm missing something fundamental in my view of how binding works.
I assume that since I've set the DataContext of my MainWindow to a ViewModel in code-behind, that all of the binding in MainWindow.xaml would assume source of this DataContext unless otherwise specified. This does not seem to be the case when I'm using my UserControl (which itself has a ViewModel driving it)
My scenario is best described in code:
MainWindow.xaml.cs
private ViewModels.MainMenuViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new ViewModels.MainMenuViewModel();
this.DataContext = vm;
}
MainWindow.xaml (using the data-context set in code-behind)
x:Class="Mediafour.Machine.EditorWPF.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:Machine.EditorWPF.Views"
xmlns:local="clr-namespace:Machine.EditorWPF"
xmlns:localVM="clr-namespace:Machine.EditorWPF.ViewModels"
Title="MainWindow" Height="350" Width="650">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<uc:MachineTreeView x:Name="MachineTreeView" Grid.Column="0" MachineDocument="{Binding Path=CurrentDocument}" />
MainWindowViewModel.cs
public class MainWindowViewModel : ObservableObject
{
public MainWindowViewModel()
{
OpenMachine(#"D:\Projects\Agnes\EditorWPF\Test.machine");
}
private void OpenMachine(string filePath)
{
MachineDocument currentDocument = MachineDocument.OpenFile(filePath);
CurrentDocument = currentDocument;
}
private MachineDocument _currentDocument;
public MachineDocument CurrentDocument
{
get { return _currentDocument; }
set
{
if (_currentDocument != null)
{
_currentDocument.Dispose();
_currentDocument = null;
}
_currentDocument = value;
base.RaisePropertyChanged("CurrentDocument"); //this fires
}
}
Using this approach, the binding statement in MainWindow.xaml errors out. Looking at Snoop binding error, it states that the CurrentDocument property is not found in MachineViewModel
System.Windows.Data Error: 40 : BindingExpression path error: 'CurrentDocument' property not found on 'object' ''MachineViewModel' (HashCode=27598891)'. BindingExpression:Path=CurrentDocument; DataItem='MachineViewModel' (HashCode=27598891); target element is 'MachineTreeView' (Name='MachineTreeView'); target property is 'MachineDocument' (type 'MachineDocument')
Why is it looking at the MachineViewModel when the binding is done in MainWindow?
Other binding properties in MainWindow do work as expected, this is the only UserControl binding I have though.

Either it is a simple mistake
you're setting MainMenuViewModel instead of MainWindowViewModel as MainWindow.DataContext
or maybe
you set the DataContext for the UserControl in the wrong manner. Take a look at this Simple Pattern for Creating Re-useable UserControls to do it the right way.

Related

How to bind a collection of a subtype to a dependency property

I'm creating a UserControl in WPF, that is able to work for any object of type IMyNode. Basically, it receives an ObservableCollection through a dependency property, register to it and do some stuff.
In one of my usecase, I use in a control that uses(and need), an ObservableCollection of SomeSpecificNode. SomeSpecificNode is an implementation of IMyNode.
Currently, I've a binding error:
System.Windows.Data Error: 1 : Cannot create default converter to perform 'one-way' conversions between types 'System.Collections.ObjectModel.ObservableCollection`1[SomeSpecificNode]' and 'System.Collections.ObjectModel.ObservableCollection`1[IMyNode]'.
I understand why it happens, it doesn't know how to convert automatically an ObservableCollection<SomeSpecificNode> to ObservableCollection<IMyNode>.
What would be the correct approach to do this?
Using a converter would break the NotifyPropertyChange. Using a ObservableCollection<IMyNode> in my parent ViewModel would not work for the other control in the same page.
Thank you!
Here some pseudo code:
public class SomeSpecificNode: IMyNode{
}
public interface IMyNode{
}
public class ParentViewModel {
public ObservableCollection<SomeSpecificNode> SelectedNodes {get;}=> new ObservableCollection<SomeSpecificNode>()
}
<UserControl x:Class="ParentView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ch.VibroMeter.Xms.Configurators.Controls.ActionBar"
xmlns:dxb="http://schemas.devexpress.com/winfx/2008/xaml/bars"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<ParentViewModel/>
</UserControl.DataContext>
<StackPanel Orientation="Horizontal" Name="RootContainer">
<SomeChildControl Nodes="{Binding SelectedNodes}" /><!-- This binding will fail !-->
</StackPanel
</UserControl>
public partial class ParentView : UserControl
{
public static readonly DependencyProperty NodesProperty = DependencyProperty.Register(
nameof(Nodes), typeof(ObservableCollection<IMyNode>), typeof(ParentView), new PropertyMetadata(default(ObservableCollection<IMyNode>), OnNodesChanged));
private static void OnNodesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//...
}
public ObservableCollection<IMyNode> Nodes
{
get { return (ObservableCollection<IMyNode>)GetValue(NodesProperty); }
set { SetValue(NodesProperty, value); }
}
}
You should change the type of the dependency property to a compatible type such as IEnumerable<IMyNode>.
You cannot set an ObservableCollection<IMyNode> property to anything else than an ObservableCollection<IMyNode> or null.
An ObservableCollection<SomeSpecificNode> is not an ObservableCollection<IMyNode> but it is an IEnumerable<IMyNode> assuming that SomeSpecificNode implements IMyNode.
So this compiles just fine;
IEnumerable<IMyNode> collection = new ObservableCollection<SomeSpecificNode>();
But this doesn't:
ObservableCollection<IMyNode> collection = new ObservableCollection<SomeSpecificNode>(); //Cannot implictly convert type...
The difference is that IEnumerable<T> is covariant. Please refer to the docs for more information.

WPF vs. Silverlight - DataBinding in resources

after some time of silverlight-development I am currently doing some WPF work...
I often used this trick to make my life easier in some of my ValueConverters:
public class MyCovnerterWithDataContext : FrameworkElement, IValueConverter
{
private MyDataContextType Data
{
get { return this.DataContext as MyDataContextType; }
}
....
Now I could access my DataContext in the Converter-Method, which comes handy in lots of situations as you can imagine.
I tried the same trick in WPF and found out, that unfortunately this does not work at all. There is the following error in the debug-output:
"Cannot find element that provides DataContext"
I suppose the resources aren't part of the visual tree in WPF whereas they are in Silverlight.
So - is my little trick possible in WPF as well?
Is my little trick to be considered a dirty hack?
What's your opinion and suggestions?
Regards
Johannes
Update:
as requested some more info - actually a minimal example:
XAML:
<Window x:Class="WpfDataContextInResources.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfDataContextInResources"
x:Name="window"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:TestWrapper x:Key="TestObj" />
</Window.Resources>
<StackPanel>
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding DataContext.Text, Source={StaticResource TestObj}, FallbackValue='FALLBACK'}" />
</StackPanel>
</Window>
the .cs file:
namespace WpfDataContextInResources
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new DataClass()
{
Text = "Hello",
};
}
}
public class TestWrapper : FrameworkElement {}
public class DataClass
{
public string Text { get; set; }
}
}
At least on my PC the lower text-block stays on the fallbackvalue
Update #2:
I tried the suggestion Matrin posted (deriving from DependencyObject, creating own DependencyProperty, etc) - it did not work either.
This time however the error-message is a different one:
"System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:(no path); DataItem=null; target element is 'TestWrapper' (HashCode=28415924); target property is 'TheData' (type 'Object')"
I also have some suggestions for workarounds though:
1.) - Use MultiBinding --> not compatible with Silverlight, not enough in some cases.
2.) - Use yet another wrapping object, set DataContext by hand in code-behind, like this --> fully compatible with Silverlight (apart from the fact, that you can't use a Framework-Element directly - you have to make an empty class deriving from it)
xaml:
<Window.Resources>
<FrameworkElement x:Key="DataContextWrapper" />
<local:TestWrapper x:Key="TestObj" DataContext="{Binding DataContext, Source={StaticResource DataContextWrapper}}" />
...
code behind:
//of course register this handler!
void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var dcw = this.Resources["DataContextWrapper"] as FrameworkElement;
dcw.DataContext = this.DataContext;
}
There may be a problem with your type being derived from FrameworkElement:
From the msdn page about suitable objects in ResourceDictionaries
[...] Being shareable is required [...]
Any object that is derived from the UIElement type is inherently not
shareable [...]
Derive from DependencyObject instead:
public class TestWrapper : DependencyObject {}

Setting UserControl ViewModel Property

All -
I am using Unity in my WPF application for DI (without prism). I have my MainWindow.xaml and MainWindowViewModel.cs. I have a usercontrol in my Mainwindow.xaml. The user control has its own uc1.xaml and uc1viewmodel.cs. The UC1 ViewModel is currently exposed as a property on MainWindowViewModel so I can set the datacontext on the usercontrol (as recommended by many ppl here).
The question I have is how/where can I set this property - will it be in app.xaml.cs or will it be in the constructor of mainwindowviewmodel. Code Snippets:
App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
//Step 1 - One Time - Creating an instance of the container
UnityContainer unity = new UnityContainer();
//Step 2 - Registering your MainWindowViewModel
unity.RegisterType<IViewModel, UserControl1ViewModel>();
//Step 3 - Creating an Instance
UserControl1ViewModel uc1_mwvm = unity.Resolve<UserControl1ViewModel>(); <-- doesnt help
MainWindowViewModel mwvm = unity.Resolve<MainWindowViewModel>();
MainWindow mw = unity.Resolve<MainWindow>();
mw.Show();
}
MainWindowViewModel.cs
public class MainWindowViewModel
{
public IViewModel IVM { get; set; }
public MainWindowViewModel()
{
//IVM = new UserControl1ViewModel(); <-- All I really want is an equivalent but letting Unity do the work.
}
}
MainWindow.xaml
<Window x:Class="_05_ViewFist_UC_Unity_Working.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc1="clr-namespace:_05_ViewFist_UC_Unity_Working"
xmlns:uc2="clr-namespace:_05_ViewFist_UC_Unity_Working"
Title="MainWindow" Height="350" Width="525">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding NNN}" />
<uc1:UC1 DataContext="{Binding UC1VM}" />
<uc2:UC2 DataContext="{Binding UC2VM}" />
</StackPanel>
</Window>
UC1
<UserControl x:Class="_05_ViewFist_UC_Unity_Working.UC1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" >
<StackPanel Orientation="Horizontal" Background="Red">
<TextBlock Text="UC1 " />
<TextBlock Text="{Binding FirstName}" />
</StackPanel>
</UserControl>
As you see from the code - Instance of UC1 is created in xaml (MainWindow.xaml) and hence when MainWindow instance is created in app.xaml.cs - it still doesnt create an instance of UserControl1ViewModel.
Question again is : Dont think its a good practice for me to call the Unity Resolve statement in the constructor of MainwindowViewModel. Is that correct??
Can somebody share a code snippet of how/where I can do this?
Thanks
I downloaded your solution from github and tried to solve your problem.
You did a great job just you forgot few details such as property attributes.
This is how your App.cs file shall look alike:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
//Step 1 - One Time - Creating an instance of the container
UnityContainer unity = new UnityContainer();
//Step 2 - Registeration
unity.RegisterType<IMainWindowViewModel, MainWindowViewModel>();
unity.RegisterType<IUC1ViewModel, UC1ViewModel>();
unity.RegisterType<IUC2ViewModel, UC2ViewModel>();
//// Instance of MainWindowViewModel will be created once you call Resolve MainWindow.
MainWindow mw = unity.Resolve<MainWindow>();
mw.Show();
}
Here is what I changed:
public class MainWindowViewModel : IMainWindowViewModel
{
#region Public Properties
[Dependency]
public IUC1ViewModel UC1VM { get; set; }
[Dependency]
public IUC2ViewModel UC2VM { get; set; }
public string NNN { get; set; }
#endregion
public MainWindowViewModel()
{
NNN = "This value coming from MainWindowViewModel";
}
}
[Dependency] is a property attibute that tells Unity where to inject values.
I could merge my code to your repo in github if you wish so.
Let me know if this helped you any futher. Feel free to mark this as the answer.
You can use the service locator pattern. I use it with Unity as a DI.
internal class ServiceLocator
{
[...]
public MainViewModel Main { get { return container.Resolve<MainViewModel>(); } }
}
You can intantiate your class the way you want (DI or not, the class initializes the DI or receive it as a parameter, you can store the DI in a private static property, you can initialize your class if DI is null or when the application starts etc...).
In your App.xaml
<Application.Resources>
<vm:ServiceLocator x:Key="Locator"/>
</Application.Resources>
And now, you can set your datacontext
DataContext="{Binding Main, Source={StaticResource Locator}}"
Edit:
I found another way of doing it (among other):
Take a look at this article. In the command, you can resolve your viewmodel as you like.

Bind the tab header to a property from code

I am binding a collection to a TabControl using its ItemSource property.
I'm programming WPF in code and not in XAML to get a deeper understanding.
The problem I'm faced with is that if I want to bind the header of a TabItem to a property ("EntityID") the binding does not kick in.
The code works if I set a value instead of a binding (code below in comments)
var binding = new Binding();
binding.Path = new PropertyPath("EntityID");
DataTemplate itemTemplate = new DataTemplate();
var label = new FrameworkElementFactory(typeof(Label));
//label.SetValue(Label.ContentProperty,"test");
label.SetBinding(Label.ContentProperty, binding);
itemTemplate.VisualTree = label;
_tabControl.ItemTemplate = itemTemplate;
Furthermore, if set the ContentTemplate instead of the ItemTemplate the binding works as well.
How can I bind the tab header to a property of my ItemsSource from purely code?
There are many ways to set bindings from Code Behind. What you should try to bind is the HeaderProperty on the TabItem. Though you must first retrieve it to do that.
Here is a working example that should set you started. It's not the way I would do it, as I would do that in xaml, but as you requested to do that from code behind, here you go :)
On a side note, it's almost always a bad idea to define templates in Code behind, try to avoid it as much as possible.
Windows.xaml
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:StackOverflow"
Title="Super long title of the super window" Width="500" Height="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Entity}">
<TextBlock Text="{Binding Id}" FontSize="{Binding Size}" />
</DataTemplate>
</Window.Resources>
<local:MyTabControl x:Name="tabControl" ItemsSource="{Binding Entities}" />
</Window>
Window.xaml.cs
using System.Windows;
using System;
using System.Windows.Data;
using System.Collections.Generic;
using System.Windows.Controls;
namespace StackOverflow
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public IEnumerable<Entity> Entities
{
get
{
for (int i = 0; i < 5; i++)
{
yield return new Entity() { Id = i };
}
}
}
}
public class MyTabControl : TabControl
{
protected override DependencyObject GetContainerForItemOverride()
{
var tabitem = base.GetContainerForItemOverride() as TabItem;
if (tabitem != null)
{
tabitem.Loaded += OnTabItemLoaded;
}
return tabitem;
}
void OnTabItemLoaded(object sender, RoutedEventArgs e)
{
var tabItem = sender as TabItem;
if (tabItem == null)
throw new InvalidOperationException();
tabItem.SetBinding(TabItem.HeaderProperty, new Binding("DisplayName"));
}
}
public class Entity : DependencyObject
{
public int Id { get; set; }
public string DisplayName { get { return "Entity ID : " + Id; } }
}
}
Couple of things...
As a WPF designer and developer, XAML is the best way of GUI and Code
Behind segregation. It does not decrease my understanding of WPF in
any way. So I recommend XAML.
Believe me being a Winforms / ASP.NET developer myself, I was initially reluctant of using XAML, but the titanic amount of code that I had to write and the relationships between various GUI elements and upon that taming the beasts called Templates \ Styles \ Triggers and ResourceDictionaries just using C# code behind was just a plain torture to me.
Enough of my experience, this is about your issue!!
To answer your question, have you set _tabControl.ItemsSource? And make sure that each item from that ItemsSource has EntityID property in it. Your code should work.
If this still doesnt work then try to see in your Visual Studio's Output window, if there are any binding errors.

WPF MVVM binding to DependencyProperty of a UserControl not working

I am trying to stick close to MVVM approach to building my WPF app and am running into a weird binding issue and feel like I'm missing something.
I have a user control (PluginsTreeView) which has a ViewModel (PluginsViewModel) driving it. PluginsTreeView exposes a public DependencyProperty of type string (DocumentPath). My MainWindow set's this property in XAML, but it doesn't seem to make it to my UserControl. I'm looking for some indication as to why this doesn't work.
PluginsTreeView.xaml.cs
public partial class PluginsTreeView: UserControl
{
public PluginsTreeView()
{
InitializeComponent();
ViewModel = new ViewModels.PluginsViewModel();
this.DataContext = ViewModel;
}
public static readonly DependencyProperty DocumentPathProperty =
DependencyProperty.Register("DocumentPath", typeof(string), typeof(PluginsTreeView), new FrameworkPropertyMetadata(""));
public string DocumentPath
{
get { return (string)GetValue(DocumentPathProperty); }
set
{
//*** This doesn't hit when value set from xaml, works fine when set from code behind
MessageBox.Show("DocumentPath");
SetValue(DocumentPathProperty, value);
ViewModel.SetDocumentPath(value);
}
}
....
}
MainWindow.xaml
My PluginsTreeView never gets the value 'test path' and I'm not sure why. I feel like I'm missing something fundamental here.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Views="clr-namespace:Mediafour.Machine.EditorWPF.Views" x:Class="Mediafour.Machine.EditorWPF.MainWindow"
xmlns:uc="clr-namespace:Mediafour.Machine.EditorWPF.Views"
Title="MainWindow" Height="350" Width="600">
<Grid>
<uc:PluginsTreeView x:Name="atv" DocumentPath="from xaml" />
</Grid>
</Window>
But, when I set the DependencyProperty from the code-behind of the MainWindow, it does seem to set the value correctly. I'm trying to figure out the difference here and why the code-behind approach works and setting the property in xaml doesn't.
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainWindowViewModel ViewModel = new MainWindowViewModel();
this.DataContext = ViewModel;
atv.DocumentPath = "from code behind"; //THIS WORKS!
}
....
}
Messing around with Snoop, I see that the value from XAML "from xaml" does make it into the property but my Set method in PluginsTreeView still never gets hit. The message box I have in there as a debug tool doesn't pop unless value is set from MainWindow code-behind.
Apparently you should not add any logic to these properties setters, because they are only called when you set the property from code. If you set the property from XAML the SetValue() method is called directly. I've ended up registering a callback method and all works great now:
public static readonly DependencyProperty DocumentPathProperty = DependencyProperty.Register("DocumentPath", typeof(string), typeof(PluginsTreeView), new FrameworkPropertyMetadata("initial value", OnValueChanged));

Resources