Binding combobox selected value to an application setting - wpf

Having application properties mapped like this:
<Application.Resources>
<properties:Settings x:Key="Settings" />
</Application.Resources>
The goal is to bind font size setting MainWindowFontSize (int) to a selected value on combobox:
<ComboBox
SelectedValuePath="Content"
SelectedValue="{Binding Default.MainWindowFontSize, Source={StaticResource Settings}}">
<ComboBoxItem>8</ComboBoxItem>
...
<ComboBoxItem>48</ComboBoxItem>
</ComboBox>
The issue with this is that it works only in one direction, from the setting into ComboBox, but any selection in the combo is not going back to the setting.
Everything seems to work fine when I use a regular property for font size in a model...
Any suggestions on how to make the binding to work with a setting both ways?

It looks to be something new in .NET 4.5. I have found though that if you create binding in the code behind it works just fine. Like so:
public MainWindow()
{
InitializeComponent();
var binding = new Binding("Delay");
binding.Source = Settings.Default;
binding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(this.Combo, ComboBox.SelectedValueProperty, binding);
}

Have you tried setting your binding's Mode to TwoWay?
<ComboBox
SelectedValuePath="Content"
SelectedValue="{Binding Default.MainWindowFontSize, Source={StaticResource Settings}, Mode=TwoWay}">
You can try the UpdateSourceTrigger, also:
<ComboBox
SelectedValuePath="Content"
SelectedValue="{Binding Default.MainWindowFontSize, Source={StaticResource Settings}, Mode=TwoWay}, UpdateSourceTrigger=PropertyChanged">

Found this workaround:
<ComboBox ... SelectionChanged="MainWndFontSizeSelectionChanged" ...>
The event handler:
private void MainWndFontSizeSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var cb = (ComboBox)sender;
int newSize = 0;
if (Int32.TryParse(cb.SelectedValue.ToString(), out newSize) == true)
{
WpfApplication1.Properties.Settings.Default.MainWindowFontSize = newSize;
}
}
Ugly, but works... Hoping for a better solution to come up...
This post provides more insight into the issue as it appears:LINK
It does not work the same way in .NET4.5 as in the previous versions.

Related

Data Binding works in code-behind, but not in XAML Possibly ItemsControl Issue

I have several bindings in my code, but I'm not sure why only this one fails in xaml.
<ItemsControl.ItemTemplate>
<DataTemplate>
<wpfExp:SignalGraph
x:Name="signal_graph"
Height="{Binding ElementName=signal_box, Path=GraphHeight, Mode=OneWay}"
<!--PenWidth="{Binding ElementName=signal_box, Path=GraphPenWidth, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"-->
Signal="{Binding}"
signal_graph_window_width="{Binding ElementName=signal_box, Path=signal_graph_window_width, Mode=OneWay}"
X_Scale="{Binding ElementName=signal_box, Path=X_Scale, Mode=OneWay}"
MaxTimeValue="{Binding Source = {StaticResource ResourceKey=signal_data}, Path = MaxTimeValue, Mode=OneWay}"
/>
only the commented line fails. Meanwhile, if I go into code behind:
private void SignalGraph_Loaded(object sender, RoutedEventArgs e)
{
loaded = true;
Binding b1 = new Binding();
b1.ElementName = "signal_box";
b1.Path = new PropertyPath("GraphPenWidth");
b1.Mode = BindingMode.OneWay;
b1.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
this.SetBinding(PenWidthProperty, b1);
Width = signal_graph_window_width;
SignalBox sb = VisualTreeUtilities.FindVisualParent<SignalBox>(this);
DrawSignals();
}
this works. To me they both seem the same.
But the only possible explanation that I can think of is that maybe when I write the binding in xaml, the Binding takes place at a different time rather than on load event like the code behind. If it is too early, before signalgraph is actually created, then maybe it doesn't bind correctly? but i'm not getting a binding error message. Quite confused.
So I figured out what was going on. Basically I accidentally had set a value to the DependencyProperty using the .net property setter and it was overwriting the binding. When I did the bind in code behind, I put the binding in the loaded event block, but in xaml, the binding was occurring in the constructor for the higher level control, so it would get overwritten later.
thanks for all the ideas and help thinking about it

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} />

Bbinding combobox within dataform to view model property outside dataform's context

I have two properties in my view model:
//Relationship has property ReasonForEndingId
private Relationship editRelationship;
public Relationship EditRelationship
{
get
{
return editRelationship;
}
set
{
if (editRelationship != value)
{
editRelationship = value;
RaisePropertyChanged(EditRelationshipChangedEventArgs);
}
}
}
//ReasonForLeaving has properties Reason & Id
private IList<ReasonForLeaving> reasonsComboList { get; set; }
public IList<ReasonForLeaving> ReasonsComboList
{
get
{
return reasonsComboList;
}
private set
{
if (reasonsComboList != value)
{
reasonsComboList = value;
RaisePropertyChanged(ReasonsComboListChangedEventArgs);
}
}
}
In my xaml I have the following: (specifically note the binding on the dataform and combobox)
<toolkit:DataForm x:Name="EditForm" CurrentItem="{Binding EditRelationship, Mode=TwoWay}">
<toolkit:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<toolkit:DataField>
<ComboBox x:Name="EndReasonCombo" ItemsSource="{Binding ReasonsComboList}" DisplayMemberPath="Reason" SelectedValuePath="Id" SelectedValue="{Binding ReasonForEndingId, Mode=TwoWay}"/>
</toolkit:DataField>
So, I'm trying to bind to a list that exists in my viewmodel (the datacontext for the page). However, the DataForm's datacontext is EditRelationship. ReasonsComboList does not exist within EditRelationship.
How can I bind the combobox so that it will display the list of items available in ReasonsComboList?
Thanks for your help!
Here's what I did (tested and works):
Within a DataForm this won't work (because its a DataTemplate) :
<ComboBox MinWidth="150" DisplayMemberPath="Name" Name="cbCompanies"
SelectedItem="{Binding TODOCompany,Mode=TwoWay}"
ItemsSource="{Binding ElementName=control, Path=ParentModel.Companies}" />
But you can do this instead:
<ComboBox MinWidth="150" DisplayMemberPath="Name" Name="cbCompanies"
SelectedItem="{Binding TODOCompany,Mode=TwoWay}"
Loaded="cbCompanies_Loaded"/>
Code behind:
private void cbCompanies_Loaded(object sender, RoutedEventArgs e)
{
// set combobox items source from wherever you want
(sender as ComboBox).ItemsSource = ParentModel.Companies;
}
if you set your datacontext using locator in this way
DataContext="{Binding FormName, Source={StaticResource Locator}}"
<ComboBox ItemsSource="{Binding FormName.ReasonsComboList, Source={StaticResource Locator}, Mode=OneWay}"
DisplayMemberPath="Reason" SelectedValuePath="Id"
SelectedValue="{Binding ReasonForEndingId, Mode=TwoWay}"/>
tested and working
I haven't tested this with your exact scenario, but you should be able to reference the DataContext of some parent element when binding the ItemsSource of the ComboBox. Basically using Silverlight's element-to-element binding to actually bind to some property on the parent container's DataContext instead of the current element's DataContext.
For example, if your main ViewModel was the DataContext of the LayoutRoot element you should be able to do something like this:
<ComboBox x:Name="EndReasonCombo" ItemsSource="{Binding DataContext.ReasonsComboList, ElementName=LayoutRoot}" DisplayMemberPath="Reason" SelectedValuePath="Id" SelectedValue="{Binding ReasonForEndingId, Mode=TwoWay}"/>
Creating a Silverlight DataContext Proxy to Simplify Data Binding in Nested Controls
Disclaimer: This may not actually work for the DataForm, but is suitable for the same problem when using a DataGrid. but I'm putting it here as an answer because it was an interesting read and helped me understand some things when I experienced the same problem.

IsSynchronizedWithCurrentItem attribute and current item updates

I have a view model to manage a dialog type of view that allows filtering of a listing (if necessary) and selection of an item. The code works fine whether I set IsSynchronizedWithCurrentItem to true or not. My understanding is that this property is not true by default in a ListView, so I'd like to better understand this property.
Here is the binding setup in the view's xaml (which works just as well without the synch property setting):
<ListView
ItemsSource="{Binding Projects.View}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedProject, Mode=TwoWay}"
>
The view model Projects is actually a CollectionViewSource that is backed by a private ObservableCollection. I think I glommed this idea from a sample project of Josh Smith's, but I honestly don't recall right now. Here is the relevant portion of the VM that relates to the xaml binding:
private ObservableCollection<ProjectViewModel> _projectsInternal { get; set; }
public CollectionViewSource Projects { get; set; }
private void _populateProjectListings(IEnumerable<Project> openProjects) {
var listing = (from p in openProjects
orderby p.Code.ToString()
select new ProjectViewModel(p)).ToList();
foreach (var pvm in listing)
pvm.PropertyChanged += _onProjectViewModelPropertyChanged;
_projectsInternal = new ObservableCollection<ProjectViewModel>(listing);
Projects = new CollectionViewSource {Source = _projectsInternal};
}
/// <summary>Property that is updated via the binding to the view</summary>
public ProjectViewModel SelectedProject { get; set; }
The Filter property of the CollectionViewSource has a handler which returns various predicates on the view model items in the list which is picked up by the bindings correctly. Here is the gist of that code (which is in the same ProjectSelctionViewModel):
/// <summary>Trigger filtering of the <see cref="Projects"/> listing.</summary>
public void Filter(bool applyFilter)
{
if (applyFilter)
Projects.Filter += _onFilter;
else
Projects.Filter -= _onFilter;
OnPropertyChanged<ProjectSelectionViewModel>(vm=>vm.Status);
}
private void _onFilter(object sender, FilterEventArgs e)
{
var project = e.Item as ProjectViewModel;
if (project == null) return;
if (!project.IsMatch_Description(DescriptionText)) e.Accepted = false;
if (!project.IsMatch_SequenceNumber(SequenceNumberText)) e.Accepted = false;
if (!project.IsMatch_Prefix(PrefixText)) e.Accepted = false;
if (!project.IsMatch_Midfix(MidfixText)) e.Accepted = false;
if (!project.IsAvailable) e.Accepted = false;
}
Setting the Mode=TwoWay is redundant since the ListView's SelectedItem binding is two way by default, but I don't mind being explicit about it (I might feel differently about that once I understand WPF better).
What about my code is making the IsSynchronizedWithCurrentItem=True redundant?
My gut is that this is decent code, but I don't like that pieces of it seem to be working via "magic", which means I would welcome any constructive feedback!
Cheers,
Berryl
IsSynchronizedWithCurrentItem syncs the CurrentItem of the default CollectionView of the bound collection with the SelectedItem of your control.
Since you never use the CurrentItem of the CollectionView and you do not appear to bind to the same collection twice, setting the property in question has no visible effect at all.
Demo of how the property syncs (for XAML viewers like XAMLPad):
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<x:Array x:Key="Items" Type="{x:Type sys:String}">
<sys:String>Apple</sys:String>
<sys:String>Orange</sys:String>
<sys:String>Pear</sys:String>
<sys:String>Lime</sys:String>
</x:Array>
</Page.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<StackPanel Background="Transparent">
<ListBox IsSynchronizedWithCurrentItem="True" ItemsSource="{StaticResource Items}" />
<ListBox IsSynchronizedWithCurrentItem="True" ItemsSource="{StaticResource Items}" />
<!-- This TextBlock binds to the CurrentItem of the Items via the "/" -->
<TextBlock Text="{Binding Source={StaticResource Items}, Path=/}"/>
</StackPanel>
</ScrollViewer>
</Page>

Problem with SelectedItem of WPF Editable ComboBox with DataTemplate

I’m having the following issue with WPF ComboBox:
XAML:
<Window.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type this:Data}">
<ComboBox IsTextSearchEnabled="False" IsEditable="True"
Text="{Binding Value}" ItemsSource="{Binding Menu}"/>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding}"/>
<Button Click="ChangeData_Click">Change Data</Button>
</StackPanel>
Code behind:
public Window1()
{
InitializeComponent();
DataContext = new Data();
}
void ChangeData_Click(object sender, RoutedEventArgs e)
{
DataContext = new Data();
}
I open the window and get ComboBox, bounded to my data model, I select some item (e.g. 1), all is dandy.
I change the data context to a new data model – the selected item is (to my surprise) 1... Where I don't expect any selected item...
I suspect it has something to do with the combo box which search disabled and editable, but I’m not sure what was the problem.
I found a work around: call UpdateLayout() on the ContentControl bounded to the DataContext, but it’s ugly.
Is that WPF bug? Is it all my fault?
Please Help
I've submitted the same question to MSDN WPF Forum and it seems like a Microsoft bug.
There's a workaround I found, ugly, but it's working. Here's the modified code behind:
public Window1()
{
InitializeComponent();
DataContext = new Data();
DataContextChanged += delegate { contentControl.UpdateLayout(); };
}
void ChangeData_Click(object sender, RoutedEventArgs e)
{
DataContext = null;
DataContext = new Data();
}
Note that both setting the DataContext to null and calling UpdateLayout() on DataContextChanged are needed to solve this issue.

Resources