How can I set ComboBox DataContext in XAML? - wpf

I am trying to understand Binding so I have come up with a very simple program to try and test it.
I have the following element in my MainWindow:
<ComboBox Name="comboBox1" ItemsSource="{Binding}" />
In my Code I have the following observable collection:
public ObservableCollection<string> ComboItems = new ObservableCollection<string>();
I can successfully add items this way at any point during runtime:
comboBox1.DataContext = ComboItems;
ComboItems.Clea();
ComboItems.Add("Item");
My question is, how could I set the DataContext in XAML so that I don't have to do it in code? Is it possible?
Thank you!

Something common is:
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}">
<ComboBox ItemsSource="{Binding ComboItems}" .../>
However usually you want to inject another object instance as DataContext, have a look at the MVVM pattern for example. Properties in WPF are inherited, so the ComboBox has the DataContext of the Window, it can be overwritten at any level though.
Note: For the Binding to work ComboItems needs to be a public property, not a field.
Resources of interest:
Data Binding Overview
Data Templating Overview
Depedency Properties Overview

Related

How can I bind to the design instance of the data context

How can I do a binding like this?
<local:MySubView d:DataContext="{Binding MyParentView.(d:DesignProperties.DataContext)}"/>
I see MySubView doesn't get the DataContext of MyParentView automatically. The syntax above gives the DataContext is unknown.
The MySubView is used as DataTemplate inside MyParentUserControl.Resources. Both user controls should have the same view model. When I create a new instance like I do in MyParentView it is working:
<local:MySubView d:DataContext="{d:DesignInstance local:MyParentView, IsDesignTimeCreatable=True}"/>
But how can I bind to the same instance?
This Post was the answer. I had the same scenario. The binding was missing on an other place:
<ContentControl Content="{Binding}" Style="{StaticResource MyContentControlStyle}" >

Adding resource in code and using it as StaticResource

For some reason I am unable to bind to ViewModel properties within DataTemplates on some controls. The result of the binding itself is unpredictable, sometimes it work, sometimes it doesn't. For this reason I am thinking of exposing the ViewModel in some other way besides setting it as DataContext.
First thought was to add ViewModel to Resources collection. I am using TabControls for UI, so whenever a view needs to be displayed, it is done through Data templates.
<DataTemplate DataType="{x:Type vm:SomeViewModel}">
<vw:SomeView />
</DataTemplate>
In this situation the view is instantiated automatically, and its DataContext is set to ViewModel set in template. Is there a way I can make this ViewModel available to View's Resources (ex with key=viewModel), so that I can use it like this:
<TextBlock Text="{Binding SomeProperty, Source={StaticResource viewModel}}" />
I have tried adding it in code, in the Loaded event for the View:
this.Loaded += (s, e) =>
{
this.Resources.Add("viewModel", this.DataContext);
};
Above code is executed before the error pops up that says static resource is not found at run-time, so the resource was added to collection.
Any ideas what can I do?
You can define ViewModel as a resource in XAML like that:
<vm:SomeViewModel x:Key="ViewModel"/>
If you want to Bind to DataContext in a DataTemplate you can use the following:
{Binding Path=DataContext, ElementName=uc}
assuming that your window/usercontrol name is x:Name="uc" , or as #stukselbax wrote:
{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=[UserControl|Window]}}

Binding Telerik RadRichTextBox through MVVM

I can't seem to pick my way through Telerik's terrible documentation for binding through an MVVM situation. We've got the standard view, and view model. The viewmodel exposes a property, 'Body' that is supposed to represent what the user is typing. Here are the relevant lines of xaml:
<telerik:DocxDataProvider
x:Name="DocxProvider"
RichTextBox="{Binding ElementName=editor}"
Docx="{Binding Body, Mode=TwoWay,
UpdateSourceTrigger=LostFocus}" />
<telerik:RadRichTextBox
Grid.Row="1"
x:Name="editor"
Margin="0"
AllowDrop="True"
ShowComments="False"
MinWidth="800"
MinHeight="300"
MaxWidth="1024"
MaxHeight="1200"
HorizontalAlignment="Left"
HorizontalContentAlignment="Left"
Width="790"
Padding="20,20,0,20"
TabIndex="10"
Cursor="IBeam"
IsSpellCheckingEnabled="True" >
</telerik:RadRichTextBox>
But when I set a break point on Body, it never gets hit. What property do I need to bind to so that my view model actually gets the content of the text box?
Have you tried binding to Rtf property of telerik:RadRichTextBox; another thing can be the ordering of your elements, can you try declaring <telerik:RadRichTextBox before <telerik:DocxDataProvider
Also have a look at this thread on telerik forum -
http://www.telerik.com/community/forums/wpf/richtextbox/binding-document.aspx
Did you remember to set the datacontext in the constructor of the xaml.cs file:
public View(ViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
This is how you hook up the viewmodel to the view.

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 do I bind a "list" of strings to a ComboBox in WPF?

I basically want to take a bunch of names in a collection and bind them to a combobox. For example:
Bill
Jack
Bob
Kevin
and have those items in a collection and have it bound to the ComboBox. I'm not sure if the list will be updated dynamically or not, but I prefer to plan for it to be. Any help would be appreciated. I've been trying for a few hours now and can't figure it out. I want to do it in XAML and not the code-behind. In the code-behind,
MyComboBox.ItemsSource = MyObservableCollection;
works fine. I don't know how to do that in XAML though with the collection declared in the code-behind.
Thanks in advance (again), community.
*EDIT:
This is how I have the collection declared and accessible.
public ObservableCollection<string> propertynames
{
get {return _propertynames;}
}
private ObservableCollection<string> _propertynames;
The last thing I tried was this:
<Window.Resources>
<CollectionViewSource Source="{Binding propertynames}" x:Key="srcSort"/>
</Window.Resources>
....
<ComboBox x:Name="cboSort" HorizontalAlignment="Left" VerticalAlignment="Top"
Width="256" Background="WhiteSmoke" Margin="12,50,0,0" FontSize="12pt"
Height="27.28"
SelectedIndex="0"
SelectionChanged="cboWorkCenters_SelectionChanged"
ItemsSource="{Binding Path = {StaticResource srcSort}}">
</ComboBox>
....
I'm a total n00b to this stuff. Been in it about a week now, so I may have done something really obvious to a seasoned user.
*EDIT #2
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:WpfApplication1"
Title="Window1" Height="226" Width="242"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<ComboBox Margin="43,71,40,77"
Name="comboBox1"
ItemsSource="{Binding ob}" />
</Grid>
</Window>
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public ObservableCollection<string> ob
{
get
{
return _ob;
}
}
private ObservableCollection<string> _ob = new ObservableCollection<string>();
public Window1()
{
InitializeComponent();
FillObj();
//comboBox1.ItemsSource = ob;
}
private void FillObj()
{
for (int i = 1; i < 6; i++)
{
_ob.Add(i.ToString());
}
}
}
}
Made above real simple project just to see if I was doing it all wrong. This worked fine so something else must be causing it to fail.
*EDIT #3
*PROBLEM FIXED
For God's sake, I figured it out. I've been on this for HOURS and it's just silly what's caused it to fail.
The solution is this: I wasn't instantiating _propertynames when I declared it. I was querying the class properties with Linq to get the list of properties and then created _propertynames by passing ...GetProperties.ToList<...>() to the constructor. Apparently, you have to instantiate the variable so it hits during InitializeComponent. Unreal.
Once I did that and then added the items to it after the fact, it worked fine.
I wish WPF had a face so I could punch it. I know it's my ignorance of how it works, but I really could have used some kind of message.
Thanks guys for the help. Both of your suggestions were useful once I took care of the root issue.
private ObservableCollection<string> _propertynames
needs to be
private ObservableCollection<string> _propertynames = new ObservableCollection<string>()
There are countless ways of doing this. Once you've created the collection in code-behind, you can:
Call Resources.Add to add it to the window's resource dictionary, and then bind to the resource, e.g. ItemsSource="{Binding {DynamicResource MyList}}".
Give the ComboBox a name (using the x:Name attribute) and set its ItemsSource explicitly in code, e.g. MyComboBox.ItemsSource = myCollection;.
Create a class, make the collection a property of the class, and set the window's DataContext to an instance of that class and bind to it directly, e.g. ItemsSource = "{Binding MyCollectionProperty}".
Make the collection a property of the window, set the window's DataContext to this, and bind to the property (this is essentially the same technique as #3, only you're not creating a new class).
Without setting the window's DataContext, you can still reference a property on it using binding as long as you've given it a name, e.g. {Binding ElementName=MyWindow, Path=MyCollection}. (This is the same as Ross's suggestion.)
Or, without giving the window a name, you can use RelativeSource binding to find the ancestor Window and bind to a property on it. I don't have any confidence in my ability to write a working binding expression that uses RelativeSource off the top of my head, so I'll leave that as an exercise for the reader.
You can set the DataContext of the ComboBox to the instance of your collection, and then set itsItemsSource to {Binding}. You probably wouldn't do this in practice; I mention it just because it seems to be a common mistake for people to set the DataContext of a control without also setting a binding, and then wonder why content from the bound object isn't showing up.
(While I've said "window" in the above, everything I've said is also true for user controls.)
I'm sure there are at least five other ways to do this that I'm not thinking of. Binding is really, really flexible.
What have you tried so far?
I would approach it as follows, assuming the combo box is within a UserControl with a code-behind class containing the public property MyObservableCollection:
<UserControl x:Name="MyCollectionOwnerControl">
<ComboBox ItemsSource="{Binding ElementName=MyCollectionOwnerControl, Path=MyObservableCollection, Mode=OneWay}" />
</UserControl>

Resources