WPF decomposition - DataGridTemplateColumn - wpf

I am trying to move custom DataGrid column definition into a UserControl.
MyComboBoxColumn.xaml
<dg:DataGridTemplateColumn
x:Class="WpfDecomposition.MyComboBoxColumn"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WpfToolkit"
x:Name="_this"
>
<dg:DataGridTemplateColumn.Header>
<Button Content="{Binding MyHeader, ElementName=_this}" ></Button>
</dg:DataGridTemplateColumn.Header>
</dg:DataGridTemplateColumn>
MyComboBoxColumn.cs
public partial class MyComboBoxColumn : DataGridTemplateColumn
{
public MyComboBoxColumn()
{
InitializeComponent();
}
public static DependencyProperty MyHeaderProperty =
DependencyProperty.Register("MyHeader", typeof(string), typeof(MyComboBoxColumn), new PropertyMetadata("TEST"));
}
Main windows XAML:
<dg:DataGrid CanUserAddRows="True" AutoGenerateColumns="False">
<dg:DataGrid.Columns>
<my:MyComboBoxColumn />
</dg:DataGrid.Columns>
</dg:DataGrid>
I would expect to see a button "TEST" in the column's header, but instead I see the empty button. Looks like the binding is broken. What is wrong?

It's not working because it can't find an element with the name _this. I get the following error in the Output window when I debug your code in Visual Studio:
System.Windows.Data Error: 4 : Cannot
find source for binding with reference
'ElementName=_this'.
BindingExpression:Path=MyHeader;
DataItem=null; target element is
'Button' (Name='TestButton'); target
property is 'Content' (type 'Object')
As for why it can't find it - I think that is because WPF bindings use the visual tree to find the source of the binding. In this case, the MyComboBoxColumn is not in the visual tree, so therefore it can't find an element with that name.
I also tried using RelativeSource to find the element, but that didn't work either - likely for the same reason.
The only thing that I could get to work is to set the DataContext of the button to the column itself in the constructor:
public MyComboBoxColumn()
{
InitializeComponent();
this.TestButton.DataContext = this;
}
And then change the binding in the XAML:
<tk:DataGridTemplateColumn.Header>
<Button Content="{Binding Path=MyHeader}" x:Name="TestButton" />
</tk:DataGridTemplateColumn.Header>
That doesn't seem like the best way to do it, but at least it works.

If you don't want to or can't set the DataContext in the constructor (e.g. when creting columns dynamically in the code), set the column's Header property to the object you want to bind to (the data context) and then you can bind to this object in the HeaderStyle data template.
See this question for details.

Related

Best practices for WPF MVVM UserControls. Avoiding Binding problems

I am trying to be a good soldier and design some simple User Controls for use in WPF MVVM applications. I am trying (as much as possible) to make the UserControls themselves use MVVM, but I don't think the calling app should know that. The calling app should just be able to slap down the user control, perhaps set one or two properties, and perhaps subscribe to events. Just like when they use a regular control (ComboBox, TextBox, etc.) I'm having a heck of a time getting the bindings right. Notice the use of ElementName in the below View. This is instead of using DataContext. Without further ado, here is my control:
<UserControl x:Class="ControlsLibrary.RecordingListControl"
...
x:Name="parent"
d:DesignHeight="300" d:DesignWidth="300">
<Grid >
<StackPanel Name="LayoutRoot">
<ListBox ItemsSource="{Binding ElementName=parent,Path=Recordings}" Height="100" Margin="5" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=FullDirectoryName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
In the code behind (if there is a way to avoid code behind, please tell me)...
public partial class RecordingListControl : UserControl
{
private RecordingListViewModel vm = new RecordingListViewModel();
public RecordingListControl()
{
InitializeComponent();
// I have tried the next two lines at various times....
// LayoutRoot.DataContext = vm;
//DataContext = vm;
}
public static FrameworkPropertyMetadata md = new FrameworkPropertyMetadata(new PropertyChangedCallback(OnPatientId));
// Dependency property for PatientId
public static readonly DependencyProperty PatientIdProperty =
DependencyProperty.Register("PatientId", typeof(string), typeof(RecordingListControl), md);
public string PatientId
{
get { return (string)GetValue(PatientIdProperty); }
set { SetValue(PatientIdProperty, value);
//vm.SetPatientId(value);
}
}
// this appear to allow us to see if the dependency property is called.
private static void OnPatientId(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RecordingListControl ctrl = (RecordingListControl)d;
string temp = ctrl.PatientId;
}
In my ViewModel I have:
public class RecordingListViewModel : ViewModelBase
{
private ObservableCollection<RecordingInfo> _recordings = null;// = new ObservableCollection<string>();
public RecordingListViewModel()
{
}
public ObservableCollection<RecordingInfo> Recordings
{
get
{
return _recordings;
}
}
public void SetPatientId(string patientId)
{
// bunch of stuff to fill in _recordings....
OnPropertyChanged("Recordings");
}
}
I then put this control down in my main window and like so:
<Grid>
<ctrlLib:RecordingListControl PatientId="{Binding PatientIdMain}" SessionId="{Binding SessionIdMain}" />
<Label Content="{Binding PatientIdMain}" /> // just to show binding is working for non-controls
</Grid>
The error I get when I run all this is:
System.Windows.Data Error: 40 : BindingExpression path error: 'Recordings' property not found on 'object' ''RecordingListControl' (Name='parent')'. BindingExpression:Path=Recordings; DataItem='RecordingListControl' (Name='parent'); target element is 'ListBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
Clearly I have some sort of bindings problem. This is actually much further than I was getting. At least I'm hitting the code in the controls code behind:
OnPatientId.
Before, I didn't have the ElementName in the User Control and was using DataContext and was getting a binding error indicating that PatientIdMain was being considered a member of the user control.
Can someone point me to an example of using a User Control with MVVM design in a MVVM application? I would think this is a fairly common pattern.
Let me know if I can provide more details.
Many thanks,
Dave
Edit 1
I tried har07's idea (see one of the answers). I got:
If I try:
ItemsSource="{Binding ElementName=parent,Path=DataContext.Recordings}"
I get
System.Windows.Data Error: 40 : BindingExpression path error: 'Recordings' property not found on 'object' ''MainViewModel' (HashCode=59109011)'. BindingExpression:Path=DataContext.Recordings; DataItem='RecordingListControl' (Name='parent'); target element is 'ListBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
If I try:
ItemsSource="{Binding Recordings}"
I get
System.Windows.Data Error: 40 : BindingExpression path error: 'Recordings' property not found on 'object' ''MainViewModel' (HashCode=59109011)'. BindingExpression:Path=Recordings; DataItem='MainViewModel' (HashCode=59109011); target element is 'ListBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
I think his first idea (and maybe his second) are very close, but recall, Recordings is defined in the ViewModel, not the view. somehow I need to tell XAML to use viewModel as source. That's what setting the DataContext does, but as I said in the main part, that creates problems elsewhere (you get binding errors related to binding from the MainWindown to properties on the control).
Edit 2. If I try har07's first suggestion:
ItemsSource="{Binding ElementName=parent,Path=DataContext.Recordings}"
AND add in the code behind for the control:
RecordingListViewModel vm = new RecordingListViewModel();
DataContext = vm;
I get:
System.Windows.Data Error: 40 : BindingExpression path error: 'PatientIdMain' property not found on 'object' ''RecordingListViewModel' (HashCode=33515363)'. BindingExpression:Path=PatientIdMain; DataItem='RecordingListViewModel' (HashCode=33515363); target element is 'RecordingListControl' (Name='parent'); target property is 'PatientId' (type 'String')
in other words, the control seems fine, but the binding of the dependency proprerties to the main window seem messed up. The compiler assumes that PatientIdMain is part of RecordingListViewModel.
Various posts indicated that I couldn't set DataContext for this very reason. It would mess up bindings to the main window. See for example:
Binding to a dependency property of a user control WPF/XAML and check out Marc's answer.
You should not set x:Name in a UserControl, since the control has only one Name property, and normally that would be set through the code which uses the control. So, you can't use an ElementName Binding in order to bind to properties of the UserControl itself. Another way to bind to properties of the UserControl inside its content would be to use
{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type my:RecordingsControl}}, Path=Recordings}
In the same way, the client code which uses the control sets its DataContext, either explicitly or through inheritance. Therefore, the instance of the vm you create in the constructor is discarded and replaced by the inherited DataContext as soon as the control is displayed.
You can do two things to solve this: either create the vm outside the UserControl, e.g. as a property of the main window's ViewModel, and use the UserControl like this:
<my:RecordingsControl DataContext="{Binding RecordingListVM}"/>
This way, you wouldn't need any code behind, and the Binding above would simply change to
{Binding Recordings}
Or, create a Recordings property in the UserControl's code behind file, and bind to it as I showed in the first code example.
What you get if binding statement changed this way :
ItemsSource="{Binding ElementName=parent,Path=DataContext.Recordings}"
or this way :
ItemsSource="{Binding Recordings}"
If one of above binding way solve current binding error ("BindingExpression path error: 'Recordings' property not found..."), but lead to another binding error please post the latter error message.
I think the correct binding statement for this part is as mentioned above.
UPDATE :
Responding to your edit. Try to set DataContext locally at StackPanel level, so you can have UserControl set to different DataContext :
public RecordingListControl()
{
InitializeComponent();
RecordingListViewModel vm = new RecordingListViewModel();
LayoutRoot.DataContext = vm;
}
again I can see you've tried this but I think this is the correct way to solve particular binding error ("BindingExpression path error: 'PatientIdMain' property not found..."), so let me know if this solve the error but lead to another binding error.
One possible answer is this (inspired by Dependency property binding usercontrol with a viewmodel)
Simply use DataContext for the UserControl and don't use any of the ElementName business.
I.e.
public partial class RecordingListControl : UserControl
{
public RecordingListViewModel vm = new RecordingListViewModel();
public RecordingListControl()
{
InitializeComponent();
**DataContext = vm;**
}
....
Then, in the MainWindow, you need to bind the user control like so:
<ctrlLib:RecordingListControl
Name="_ctrlRecordings"
PatientId="{Binding RelativeSource={RelativeSource
AncestorType=Window},Path=DataContext.PatientIdMain, Mode=TwoWay}"
SessionId="{Binding RelativeSource={RelativeSource
AncestorType=Window},Path=DataContext.SessionIdMain, Mode=TwoWay}" />
I won't mark this as answer because (besides the audacity of answer one's own question) I don't really like the answer. It forces the application programmer, to remember to put in all this nonsense about AncestorType and RelativeSource. I don't think one has to do this with standard controls, so why here?

WPF tab control and MVVM selection

I have a TabControl in an MVVM WPF application. It is defined as follows.
<TabControl Style="{StaticResource PortfolioSelectionTabControl}" SelectedItem="{Binding SelectedParameterTab}" >
<TabItem Header="Trades" Style="{StaticResource PortfolioSelectionTabItem}">
<ContentControl Margin="0,10,0,5" Name="NSDetailTradeRegion" cal:RegionManager.RegionName="NSDetailTradeRegion" />
</TabItem>
<TabItem Header="Ccy Rates" Style="{StaticResource PortfolioSelectionTabItem}">
<ContentControl Margin="0,10,0,5" Name="NSDetailCcyRegion" cal:RegionManager.RegionName="NSDetailCcyRegion" />
</TabItem>
<TabItem Header="Correlations / Shocks" Style="{StaticResource PortfolioSelectionTabItem}">
<ContentControl Name="NSDetailCorrelationRegion" cal:RegionManager.RegionName="NSDetailCorrelationRegion" />
</TabItem>
<TabItem Header="Facility Overrides" Style="{StaticResource PortfolioSelectionTabItem}" IsEnabled="False">
<ContentControl Name="NSDetailFacilityOverrides" cal:RegionManager.RegionName="NSDetailFacilityOverrides" />
</TabItem>
</TabControl>
So each tab item content has its own view associated with it. Each of those views has the MEF [Export] attribute and is associated with the relevant region through view discovery, so the above code is all I need to have the tab control load and switch between them. They all reference the same shared ViewModel object behind them and so all interact seamlessly.
My problem is that when the user navigates to the parent window, I want the tab control to default to the second tab item. That is easy enough to do when the window is first loaded, by specifying in XAML IsSelected="True" in TabItem number 2. It is less easy to do when the user navigates away from the screen and then comes back to it.
I thought about having a SelectedItem={Binding SelectedTabItem} property on the tab control, so I could programmatically set the selected tab in the ViewModel, but the problem is I have no knowledge of the TabItem objects in the ViewModel as they are declared above in the XAML only, so I have no TabItem object to pass to the setter property.
One idea I had was to make the child Views (that form the content of each of the tab items above) have a style on the UserControl level of their XAML, something along the following.
<Style TargetType={x:Type UserControl}>
<Style.Triggers>
<DataTrigger Property="IsSelected" Value="True">
<Setter Property="{ElementName={FindAncestor, Parent, typeof(TabItem)}, Path=IsSelected", Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
I know the findancestor bit isn't correct; I've just put it there to specify my intent, but I am not sure of the exact syntax. Basically for each UserControl to have a trigger that listens to a property on the ViewModel (not sure how I would distinguish each different UserControl as obviously they can't all listen to the same property or they would all select simultaneously when the property is set to True, but having a property for each usercontrol seems ugly) and then finds its parent TabItem container and sets the IsSelected value to true.
Am I on the right track with a solution here? Is it possible to do what I am pondering? Is there a tidier solution?
If you look at the TabControl Class page on MSDN, you'll find a property called SelectedIndex which is an int. Therefore, simply add an int property into your view model and Bind it to the TabControl.SelectedIndex property and then you can select whichever tab you like at any time from the view model:
<TabControl SelectedIndex="{Binding SelectedIndex}">
...
</TabControl>
UPDATE >>>
Setting a 'startup' tab is even easier using this method:
In view model:
private int selectedIndex = 2; // Set the field to whichever tab you want to start on
public int SelectedIndex { get; set; } // Implement INotifyPropertyChanged here
Just FYI,
I gone through the same issue where I add tabs dynamically using ObservableCollection source but last added Tab do not get selected.
I have done same changes what Sheridan said to select Tab as per SelectedIndex. Now last added Tab gets selected but it was not getting focused.
So to focus the Tab we have to add set Binding IsAsync property True.
<TabControl ItemsSource="{Binding Workspaces}" Margin="5" SelectedIndex="{Binding TabIndex, Mode=OneWay,UpdateSourceTrigger=PropertyChanged, IsAsync=True}">
The below code sample will create a dynamic tab using MVVM.
XAML
<TabControl Margin="20" x:Name="tabCategory"
ItemsSource="{Binding tabCategory}"
SelectedItem="{Binding SelectedCategory}">
<TabControl.ItemTemplate>
<DataTemplate>
<HeaderedContentControl Header="{Binding TabHeader}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding TabContent}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Modal Class
TabCategoryItem represents each tab item. On two properties, TabHeader will display a tab caption and TabContent contains the content/control to fill in each tab.
Public Class TabCategoryItem
Public Property TabHeader As String
Public Property TabContent As UIElement
End Class
VM Class
Public Class vmClass
Public Property tabCategory As ObjectModel.ObservableCollection(Of TabCategoryItem)
Public Property SelectedCategory As TabCategoryItem
End Class
The below code will fill and bind the content. I am creating two tabs, tab1 and tab2. Both tabs will contain text boxes. You can use any UIelement instead of text boxes.
Dim vm As New vmClass
vm.tabCategory = New ObjectModel.ObservableCollection(Of TabCategoryItem)
'VM.tabCategory colection will create all tabs
vm.tabCategory.Add(New TabCategoryItem() With {.TabHeader = "Tab1", .TabContent = new TextBlock().Text = "My first Tab control1"})
vm.tabCategory.Add(New TabCategoryItem() With {.TabHeader = "Tab2", .TabContent = new TextBlock().Text = "My first Tab control2"})
mywindow.DataContent = vm
The accepted answer is not working with DependencyObject on your ViewModel .
I'm using MVVM with DependencyObject and Just setting the TabControl didn't work for me.The problem I had was the the property was not getting update on the View when I was setting the tab selectedIndex from the ViewModel.
I did set the Mode to be two ways but nothing was working.
<TabControl SelectedIndex="{Binding SelectedTab,Mode=TwoWay}" >
...
</TabControl>
The ViewModel property "SelectedTab" was getting updated all the time when I navigated between tabs. This was confirming my binding was working properly. Each time I would navigate the tabs both the Get and Set would get called in my ViewModel. But if I try to set the SelectedIndex in the ViewModel it would not update the view.
ie: SelectedTab=0 or SelectedTab=1 etc...
When doing the set from the ViewModel the SelectedTab 'set' method would be called, but the view would never do the 'get'.
All I could find online was example using INotifyPropertyChanged but I do not wish to use that with my ViewModel.
I found the solutions in this page: http://blog.lexique-du-net.com/index.php?post/2010/02/24/DependencyProperties-or-INotifyPropertyChanged
With DependencyObject, you need to register the DependencyProperties. Not for all properties but I guess for a tabcontrol property you need to.
Below my code:
view.xaml
//Not sure below if I need to mention the TwoWay mode
<TabControl SelectedIndex="{Binding SelectedTab,Mode=TwoWay}" >
...
</TabControl>
ViewModel.cs
public class ViewModel : DependencyObject
{
public static readonly DependencyProperty SelectedTabDP = DependencyProperty.Register("SelectedTab", typeof(int), typeof(ViewModel));
public int SelectedTab
{
get { return (int)GetValue(SelectedTabDP); }
set { SetValue(SelectedTabDP, value); }
}
}
Basically all I had to do was to actually register the dependency property (DependencyProperty) as you can see above.
What made this hard to figure out was that I have a bunch of other Properties on that view and I didn't need to register them like that to make it work two ways. For some reason on the TabControl I had to register the property like I did above.
Hope this help someone else.
Turns out my problem were because my components have names:
x:Name="xxxxxxxx"
Giving names to components at the same time of biding them with DependencyObject seems to be the main cause of all my issues.
In order to improve semantic of my viewmodel and to not work with an int when using code to check for the selected tab, I made some additions to the accepted answer so to use an Enum instead of an int.
These are the steps:
Define an Enum representing the different tabs:
public enum RulesVisibilityMode {
Active,
History
}
Expose the SelectedTab as a property using the enum instead of the int:
public RulesVisibilityMode SelectedTab { get; set; }
Create a converter to convert from an int to your enum (I don't need the ConvertBack because I never select the active tab from the code, but you can add it too):
internal class RulesVisibilityModeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException("Conversion from visibility mode to selected index has not been implemented");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
int selectedTabIndex;
if (int.TryParse(value.ToString(), out selectedTabIndex))
{
return (RulesVisibilityMode)selectedTabIndex;
}
return null;
}
}
Bind the tabcontrol to the SelectedTab property through the converter:
<TabControl SelectedIndex="{Binding SelectedTab, Mode=OneWayToSource, Converter={StaticResource RulesVisibilityModeConverter}}" ...
Now every time you need to check for the selected tab in the code you deal with a readable enum:
if (this.SelectedTab != RulesVisibilityMode.Active) ...

WPF Element Binding

I have two controls within my UserControl where I bind to the exact same object using Element Binding:
AllowNext="{Binding ElementName=MainGrid, Path=DataContext.CanContinue}"
On the first control it works fine but on the second I get a binding exception:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=MainGrid'. BindingExpression:Path=DataContext.CanContinue; DataItem=null; target element is 'WizardPage' (Name='DeductionPage'); target property is 'AllowNext' (type 'Boolean')
I have also tried using RelativeSource binding on the second control:
AllowNext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid}}, Path=DataContext.CanContinue}"
But this also gives me an error.
Does anyone know what this might be?
--
Here is the simplified control:
<Grid Name="MainGrid">
<w:Wizard Name="MyWizard" w:Designer.PageIndex="1" DataContext="{Binding ElementName=MainGrid, Path=DataContext.Policy}" >
<w:WizardPage Header="Main Member" MaxHeight="600" AllowNext="{Binding ElementName=MainGrid, Path=DataContext.CanContinue}" Name="MainPage">
</w:WizardPage>
<w:WizardPage Name="DeductionPage" Header="Policy Details" AllowBack="False" AllowNext="{Binding ElementName=MainGrid, Path=DataContext.CanContinue}">
</w:WizardPage>
</w:Wizard>
</Grid>
Now as I mentioned, MainPage binds fine, whereas the DeductionPage does not bind at all and gets the supplied error. The DataContext of MainGrid is set from code behind:
public void SetDataContext(object o)
{
MainGrid.DataContext = o;
}
I bet it's the MainGrid which is the binding source is not in the logical tree of your binding target.
This is the problem absolutely with the binding element. However, you didn't give the source so You want to debug it and solve the issue.
Refer the below url and "Cannot find source for binding with reference" section where explained obviously how to debug and solve it.
http://www.codeproject.com/Articles/244107/Debugging-WPF-data-bindings

Binding from items of an UserControl with custom collection property

This question is a "sequel" to this question (I have applied the answer, but it still won't work).
I'm trying to create an extended ToolBar control for a modular application, which can load its items from multiple data sources (but that is not the issue I'm trying to solve right now, now I want it to work when used as regular ToolBar found in WPF).
In short: I want the ToolBar's items to be able to bind to the tb:ToolBar's parents.
I have following XAML code:
<Window Name="myWindow" DataContext="{Binding ElementName=myWindow}" >
<DockPanel>
<tb:ToolBar Name="toolbar" DockPanel.Dock="Top" DataContext="{Binding ElementName=myWindow}>
<tb:ToolBar.Items>
<tb:ToolBarControl Priority="-3">
<tb:ToolBarControl.Content>
<StackPanel Orientation="Horizontal">
<TextBlock>Maps:</TextBlock>
<ComboBox ItemsSource="{Binding SomeProperty, ElementName=myWindow}">
Some info about the types:
tb:ToolBar is an UserControl with dependency property Items of type FreezableCollection<ToolBarControl>.
tb:ToolBarControl is an UserControl with template pretty much identical to ContentControl's template.
The problem is that the binding in the ComboBox fails (with the usual "Cannot find source for binding with reference"), because its DataContext is null.
Why?
EDIT: The core of the question is "Why is the DataContext not inherited?", without it, the bindings can't work.
EDIT2:
Here is XAML for the tb:ToolBar:
<UserControl ... Name="toolBarControl">
<ToolBarTray>
<ToolBar ItemsSource="{Binding Items, ElementName=toolBarControl}" Name="toolBar" ToolBarTray.IsLocked="True" VerticalAlignment="Top" Height="26">
EDIT 3:
I posted an example of what works and what doesn't: http://pastebin.com/Tyt1Xtvg
Thanks for your answers.
I personally don't like the idea of setting DataContext in controls. I think doing this will somehow break the data context inheritance. Please take a look at this post. I think Simon explained it pretty well.
At least, try removing
DataContext="{Binding ElementName=myWindow}"
from
<tb:ToolBar Name="toolbar" DockPanel.Dock="Top" DataContext="{Binding ElementName=myWindow}>
and see how it goes.
UPDATE
Actually, keep all your existing code (ignore my previous suggestion for a moment), just change
<ComboBox ItemsSource="{Binding SomeProperty, ElementName=myWindow}">
to
<ComboBox ItemsSource="{Binding DataContext.SomeProperty}">
and see if it works.
I think because of the way you structure your controls, the ComboBox is at the same level/scope as the tb:ToolBarControl and the tb:ToolBar. That means they all share the same DataContext, so you don't really need any ElementName binding or RelativeSource binding to try to find its parent/ancestor.
If you remove DataContext="{Binding ElementName=myWindow} from the tb:ToolBar, you can even get rid of the prefix DataContext in the binding. And this is really all you need.
<ComboBox ItemsSource="{Binding SomeProperty}">
UPDATE 2 to answer your Edit 3
This is because your Items collection in your tb:ToolBar usercontrol is just a property. It's not in the logical and visual tree, and I believe ElementName binding uses logical tree.
That's why it is not working.
Add to logical tree
I think to add the Items into the logical tree you need to do two things.
First you need to override the LogicalChildren in your tb:ToolBar usercontrol.
protected override System.Collections.IEnumerator LogicalChildren
{
get
{
if (Items.Count == 0)
{
yield break;
}
foreach (var item in Items)
{
yield return item;
}
}
}
Then whenever you added a new tb:ToolBarControl you need to call
AddLogicalChild(item);
Give it a shot.
This WORKS...
After playing around with it a little bit, I think what I showed you above isn't enough. You will also need to add these ToolBarControls to your main window's name scope to enable ElementName binding. I assume this is how you defined your Items dependency property.
public static DependencyProperty ItemsProperty =
DependencyProperty.Register("Items",
typeof(ToolBarControlCollection),
typeof(ToolBar),
new FrameworkPropertyMetadata(new ToolBarControlCollection(), Callback));
In the callback, it is where you add it to the name scope.
private static void Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var toolbar = (ToolBar)d;
var items = toolbar.Items;
foreach (var item in items)
{
// the panel that contains your ToolBar usercontrol, in the code that you provided it is a DockPanel
var panel = (Panel)toolbar.Parent;
// your main window
var window = panel.Parent;
// add this ToolBarControl to the main window's name scope
NameScope.SetNameScope(item, NameScope.GetNameScope(window));
// ** not needed if you only want ElementName binding **
// this enables bubbling (navigating up) in the visual tree
//toolbar.AddLogicalChild(item);
}
}
Also if you want property inheritance, you will need
// ** not needed if you only want ElementName binding **
// this enables tunneling (navigating down) in the visual tree, e.g. property inheritance
//protected override System.Collections.IEnumerator LogicalChildren
//{
// get
// {
// if (Items.Count == 0)
// {
// yield break;
// }
// foreach (var item in Items)
// {
// yield return item;
// }
// }
//}
I have tested the code and it works fine.
I took the pieces of Xaml that you posted and tried to reproduce your problem.
The DataContext seems to be inheriting just fine from what I can tell. However, ElementName Bindings fail and I think this has to do with the fact that even though you add the ComboBox in the Window, it ends up in a different scope. (It is first added to the Items property of the custom ToolBar and is then populated to the framework ToolBar with a Binding)
A RelativeSource Binding instead of an ElementName Binding seems to be working fine.
But if you really want to use the name of the control in the Binding, then you could check out Dr.WPF's excellent ObjectReference implementation
It would look something like this
<Window ...
tb:ObjectReference.Declaration="{tb:ObjectReference myWindow}">
<!--...-->
<ComboBox ItemsSource="{Binding Path=SomeProperty,
Source={tb:ObjectReference myWindow}}"
I uploaded a small sample project where both RelativeSource and ObjectReference are succesfully used here: https://www.dropbox.com/s/tx5vdqlm8mywgzw/ToolBarTest.zip?dl=0
The custom ToolBar part as I approximated it looks like this in the Window.
ElementName Binding fails but RelativeSource and ObjectReference Bindings work
<Window ...
Name="myWindow"
tb:ObjectReference.Declaration="{tb:ObjectReference myWindow}">
<!--...-->
<tb:ToolBar x:Name="toolbar"
DockPanel.Dock="Top"
DataContext="{Binding ElementName=myWindow}">
<tb:ToolBar.Items>
<tb:ContentControlCollection>
<ContentControl>
<ContentControl.Content>
<StackPanel Orientation="Horizontal">
<TextBlock>Maps:</TextBlock>
<ComboBox ItemsSource="{Binding Path=StringList,
ElementName=myWindow}"
SelectedIndex="0"/>
<ComboBox ItemsSource="{Binding Path=StringList,
Source={tb:ObjectReference myWindow}}"
SelectedIndex="0"/>
<ComboBox ItemsSource="{Binding Path=StringList,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
SelectedIndex="0"/>
</StackPanel>
</ContentControl.Content>
</ContentControl>
</tb:ContentControlCollection>
</tb:ToolBar.Items>
</tb:ToolBar>
Often if there is no DataContext then ElementName will not work either. One thing which you can try if the situation allows it is using x:Reference.
For that you need to move the bound control into the resources of the referenced control, change the binding and use StaticResource in the place where it was, e.g.
<Window Name="myWindow" DataContext="{Binding ElementName=myWindow}" >
<Window.Resources>
<ComboBox x:Key="cb"
ItemsSource="{Binding SomeProperty,
Source={x:Reference myWindow}}"/>
</Window.Resources>
<DockPanel>
<tb:ToolBar Name="toolbar" DockPanel.Dock="Top" DataContext="{Binding ElementName=myWindow}>
<tb:ToolBar.Items>
<tb:ToolBarControl Priority="-3">
<tb:ToolBarControl.Content>
<StackPanel Orientation="Horizontal">
<TextBlock>Maps:</TextBlock>
<StaticResource ResourceKey="cb"/>
The proper answer is probably to add everything to the logical tree as mentioned in previous answers, but the following code has proved to be convenient for me. I can't post all the code I have, but...
Write your own Binding MarkupExtension that gets you back to the root element of your XAML file. This code was not compiled as I hacked up my real code to post this.
[MarkupExtensionReturnType(typeof(object))]
public class RootBindingExtension : MarkupExtension
{
public string Path { get; set; }
public RootElementBinding(string path)
{
Path = path;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
IRootObjectProvider rootObjectProvider =
(IRootObjectProvider)serviceProvider.GetService(typeof(IRootObjectProvider));
Binding binding = new Binding(this.Path);
binding.Source = rootObjectProvider.RootObject;
// Return raw binding if we are in a non-DP object, like a Style
if (service.TargetObject is DependencyObject == false)
return binding;
// Otherwise, return what a normal binding would
object providedValue = binding.ProvideValue(serviceProvider);
return providedValue;
}
}
Usage:
<ComboBox ItemsSource={myBindings:RootBinding DataContext.SomeProperty} />

How to do simple Binding in Silverlight?

I understand that Silverlight 3.0 has binding but just want a simple example on how to use this to read a property from a class.
I have a class called Appointment which as a String property called Location:
Public Property Location() As String
Get
Return _Location
End Get
Set(ByVal Value As String)
_Location = Value
End Set
End Property
With a Private Declaration for the _Location as String of course.
I want a XAML element to bind to this property to display this in a TextElement, but it must be in XAML and not code, for example I want something like this:
<TextBlock Text="{Binding Appointment.Location}"/>
What do I need to do to get this to work?
It has to be a Silverlight 3.0 solution as some WPF features are not present such as DynamicResource which is what I'm used to using.
Just to add that my XAML is being loaded in from a seperate XAML File, this may be a factor in why the binding examples don't seem to work, as there are different XAML files the same Appointment.Location data needs to be applied.
You have two options.
If the "Appointment" class can be used as the DataContext for the control or Window, you can do:
<TextBlock Text="{Binding Location}" />
If, however, "Appointment" is a property of your current DataContext, you need a more complex path for the binding:
<TextBlock Text="{Binding Path=Appointment.Location}" />
Full details are documented in MSDN under the Binding Declarations page. If neither of these are working, make sure you have the DataContext set correctly.
You need something in code, unless you want to declare an instance of Appointment in a resource and bind to that but I doubt thats what you want.
You need to bind the Text property to the Property Path "Location" then assign the DataContext of the containing XAML to an instance of the Appointment:-
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Text="{Binding Location}" />
</Grid>
Then in the control's load event:-
void Page_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new Appointment() { Location = "SomePlace" };
}
Note in this case I'm using the default Page control.
If I'm reading correctly, you need to create an instance of Appointment, set the DataContext of the control to that instance and modify your binding to just say: Text="{Binding Location}"
Also, consider implementing INotifyPropertyChanged on your Appointment class to allow the data classes to notify the UI of property value changes.

Resources