I have a ComboBox inside a UserControl. I use the UserControl in a Window with a DataGrid: the UserControl DataContext is the DataGrid SelectedItem. The ComboBox inside the UserControl is bound to the "ID" field of the SelectedItem through the SelectedValue Property. To achieve that I programmed a DependencyProperty in the UserControl like this
Public Shared SelectedValueProperty As DependencyProperty = _
DependencyProperty.Register("SelectedValue", GetType(Object), GetType(ucEditCombo))
Public Property SelectedValue() As Object
Get
Return CType(GetValue(SelectedValueProperty), Object)
End Get
Set(ByVal value As Object)
SetCurrentValue(SelectedValueProperty, value)
End Set
End Property
and bound the ComboBox SelectedValue Property to the UserControl SelectedValue Property in XAML:
<ComboBox SelectedValuePath="{Binding ElementName=EditCombo,Path=SelectedValuePath}"
DisplayMemberPath="{Binding ElementName=EditCombo,Path=DisplayMemberPath}"
ItemsSource="{Binding ElementName=EditCombo,Path=ItemsSource}"
SelectedValue="{Binding ElementName=EditCombo,Path=SelectedValue,Mode=TwoWay}"
Visibility="{Binding ElementName=EditCombo,Path=ComboVisibility}"
Name="cmb"/>
(EditCombo is the x:Name I give to the UserControl in XAML).
Running the application I not the following:
If I change the DataGrid selection, then the ComboBox displayed value changes correctly.
As soon as I change the ComboBox selected value directly on the control, the value in the DataGrid doesn't get updated AND the ComboBox isn't bound to the DataGrid SelectedItem any longer.
The problem regarding the update of the original DataGrid SelectedItem is not apparent, although I believe it is to do with the bindings on the ComboBox. I would need to see the Window xaml and the UserControl's code behind to be sure.
I attempted to reproduce the problem with an example, but I was unable to. So to speed up your movement forward, I have posted the example below.
Assumption: The UserControl is attempting to modify properties of the object bound to the SelectedValue DependencyProperty, not attempting to replace the object. If the assumption is incorrect, update the question with the extra code that is relevant to the problem and I will attempt to update my answer.
Firstly, I have a model that I use when creating code for StackOverflow questions called ItemModel.
Public Class ItemModel
Public Property Id as Guid
Public Property Text as String
End Class
and on the Window, I have created an ObservableCollection(Of ItemModel) property called Items. It contains 3 items with each item having its Text property set to one of the values "One", "Two" or "Three". The Id of each item is set to a new guid.
So, my main window xaml looks like
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:StackOverflow._20798974"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom">
<this:ucEditCombo SelectedValue="{Binding ElementName=MyGrid, Path=SelectedItem}" />
</StackPanel>
<DataGrid x:Name="MyGrid" ItemsSource="{Binding Path=Items}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Id}"/>
<DataGridTextColumn Header="Text" Binding="{Binding Text}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Window>
The code behind of the ucEditCombo UserControl contains the DependencyProperty you have defined in the question above and an ObservableCollection(Of String) which is populated with the values "One", "Two", "Three" and "Four".
The xaml for the user control is
<UserControl x:Class="ucEditCombo" 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:this="clr-namespace:StackOverflow._20798974"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type this:ucEditCombo}}}">
<ComboBox ItemsSource="{Binding Path=Items}" SelectedValue="{Binding Path=SelectedValue.Text}" />
</Grid>
</UserControl>
The above example allows me to selected a DataGrid item and have the Text property of that item immediately appear in the ComboBox. It also allows the selection of a different value in the ComboBox and have that value updated in the DataGrid.
Points to note.
Setting the DataContext of the Control in the Controls constructor will break the SelectedItem binding on the Window. To get around this, I have set the DataContext on the first content element of the UserControl (a Grid element).
The DataContext is set to the UserControl itself, simplifying some of the bindings on the Combobox.
As we are modifying a property of a DependencyProperty, a lot of the WPF binding subsystem notifications are taken care of for us.
The default binding mode for SelectedValue and SelectedItem is TwoWay.
I hope this helps.
Related
I want to create a custom datagridcolumn with a button. I want it reusable, so I don't want to define the text of the button in the template, but in a dependency property of the column (ButtonText as string).
Here is the code I have so far (not working)
<DataGridTemplateColumn x:Class="RafColumnButton"
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:MyProject"
mc:Ignorable="d" CanUserResize="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="{Binding ButtonText, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:RafColumnButton}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I think it has something to do with datacontext, but I'm a debutant in wpf.
EDIT :here is the rafcolumnButton codebehind for the dependency property
Public Property ButtonText As String
Get
Return GetValue(ButtonTextProperty)
End Get
Set(ByVal value As String)
SetValue(ButtonTextProperty, value)
End Set
End Property
Public Shared ReadOnly ButtonTextProperty As DependencyProperty =
DependencyProperty.Register(NameOf(ButtonText),
GetType(String), GetType(RafColumnButton),
New PropertyMetadata(""))
The DataContext of the DataGridTemplateColumn.CellTemplate is the the current data item.
If the DataGrid.ItemsSource contains data items that have a property ButtonText, then the following binding will work:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="{Binding ButtonText}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Remarks
You can't bind to DataGridTemplateColumn or DataGridColumn in general. This is because DataGridColumn doesn't derive from Visual and therefore cannot be part of the visual tree. Only types that derive from abstract class Visual like UIElement can be part of the visual tree. Only types that derive from FrameworkElement can be part of the logical tree. See Microsoft Docs: Trees in WPF
DataGridColumn can be viewed as placeholder for the actual controls that makeup the column and cells.
I'll try to give a bit more details of this question
In a WPF application, on the left side of 'mainwindow' I have a UserControl containing a listBox, like this
<UserControl.Resources>
<ObjectDataProvider x:Key="viewModel" ObjectType="{x:Type vm:TemplateListViewModel}"/>
</UserControl.Resources>
<StackPanel>
<ListBox Height="Auto" Name="TemplateList" ItemsSource="{Binding Source={StaticResource viewModel}, Path=TemplateNames}"
</StackPanel>
As shown, listbox items are fetched from a xml file via viewModel.
Now I have another usercontrol2 containing a Label to echo the selection from "TemplateList".
Also under this label I have another usercontrol3 containing a datagrid, which data will be fetched from a xml file based on the label, or the selection from the listbox on the left side of the window through usercontrol3's viewmodel.
So the question is how to pass the "SelectedItem" or "SelectedIndex" to viewModel of the Label and datagrid?
I know it works when I bind a label to a listbox with 'elementName' and Path to 'SelectedItem'. Now I couldn't figure out how to do with usercontrols and 'ObjectDataProvider'. I tried as suggested below couldn't get it to work out. So far for usercontrol2, if I use the same ObjectDataProvider as the above, I can get this label to work as
<Label Name="TemplateNameLabel" Content="{Binding Source={StaticResource viewModel}, Path=TemplateNames[0]}" />
Where 'TemplateNames' if of type 'XmlNodeList' because it was read from xml file. But I really want is something like
<Label Name="TemplateNameLabel" Content="{Binding Source={StaticResource viewModel}, Path=TemplateNames[SelectedIndex]}" />
Can this be done without any command, just like binding to 'elementname' without usercontrol involved?
Thanks.
I've removed not important properties to highlight the main:
<ListBox Name="TemplateList" ItemsSource="{Binding Source={StaticResource viewModel}, Path=TemplateNames}" SelectedItem="{Binding Path=SelectedTemplate, Mode=OneWayToSource}" />
Note, that you will need SelectedTemplate property with public setter of type TemplateName - the same type that is used for single element in the TemplateNames collection in the ViewModel. In that setter you could pass the value to whatever other view-models you need.
When starting to work with WPF UserControls, I stumbled upon several ways to bind content of a UserControl to one of its properties.
Here's example C# code for my control:
public sealed partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty TheTextProperty =
DependencyProperty.Register("TheText",
typeof (string),
typeof(MyUserControl),
new FrameworkPropertyMetadata(0,
FrameworkPropertyMetadataOptions.
BindsTwoWayByDefault)
);
public string TheText
{
get { return (string)GetValue(TheTextProperty); }
set { SetValue(TheTextProperty, value); }
}
}
And here are the different ways I found to bind content to this property:
Content uses binding with RelativeSource/AncestorType
<UserControl x:Class="MyUserControl"
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">
<StackPanel>
<TextBox Text="{Binding TheText,
RelativeSource={RelativeSource
AncestorType={x:Type UserControl}}}" />
</StackPanel>
</UserControl>
DataContext of visual tree root is set to UserControl in XAML
<UserControl x:Class="MyUserControl"
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">
<StackPanel DataContext="{Binding
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}">
<TextBox Text="{Binding TheText}" />
</StackPanel>
</UserControl>
DataContext of visual tree root is set to UserControl in constructor
<UserControl x:Class="MyUserControl"
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">
<StackPanel x:Name="VisualTreeRoot">
<TextBox Text="{Binding TheText}" />
</StackPanel>
</UserControl>
Here's the constructor:
public MyUserControl()
{
InitializeComponent();
VisualTreeRoot.DataContext = this;
}
Last but not least: A warning for other people new to programming UserControls in WPF
The first time I wanted to bind content of a UserControl to one of its properties, I though "hey, let's just set the DataContext of the UserControl directly to itself":
<UserControl x:Class="MyUserControl"
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"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
Or:
public MyUserControl()
{
InitializeComponent();
this.DataContext = this;
}
However, this does not work if a user of the UserControl wants to bind its properties to other binding sources. The UserControl needs to inherit the DataContext from its parent to make this work. By overwriting it as presented above, the bindings won't find their sources anymore.
My final questions:
What are the advantages and disadvantages of each of the presented methods?
When should you use which method?
Are there more methods?
Well in the first case there is no DataContext for the TextBox set to any of it's Parent's. Hence you're having to tell the TextBox where in the VisualTree is the control with that property directly on it.
Second case DataContext is set on StackPanel which the TextBox inherit's accordingly. This is better than approach one if you have multiple control's in the StackPanel
Setting DataContext on the UserControl itself is not always wrong(via constructor or xaml). I say this because if you have 20 control's out of which 15 that need to use properties defined in it's current UserControl class and 5 that need's the parent of the UserControl's DataContext, You can always use a RelativeSource FindAncestor binding on the minority.
Only "method" I can think of that can show pt3 I mentioned is something like
<!-- Can change type to another userControl too and specify Ancestorlevel -->
<TextBlock Text="{Binding TheText, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
^^ This will work fine even if the TextBlock's parent UserControl has itself as it's DataContext
As far as when to use what.
That's just a logical choice, if you have multiple siblings needing the same DataContext, Setting DataContext to their parent is the right answer. I always tend to set DataContext on the Top-most element possible and if any one or two items need variations bind them out accordingly.
If in MVVM, your VM become the DataContext almost always of the Top level item of the View. everything else Bind's directly to the element whose property they need pretty much.
i use element binding for usercontrols when binding to their own dependency properties.
<UserControl x:Class="MyUserControl"
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"
x:Name="uc">
<StackPanel>
<TextBox Text="{Binding ElementName=uc,Path=TheText}" />
</StackPanel>
</UserControl>
I got two Comboboxes and both of them have binding with the same Source.
<ComboBox ItemsSource="{Binding Source={StaticResource UsersViewSource}}"
And when I change something in the first one, it reflects also to the second one. And I dunno how to keep their SelectedItem values separately, using the same ItemsSource.
The IsSynchronizedWithCurrentItem property should be set to False:
true if the SelectedItem is always
synchronized with the current item in
the ItemCollection; false if the
SelectedItem is never synchronized
with the current item; null if the
SelectedItem is synchronized with the
current item only if the Selector uses
a CollectionView. The default value is
null.
Here's a sample:
<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="myStrings" Type="sys:String">
<sys:String>one</sys:String>
<sys:String>two</sys:String>
<sys:String>three</sys:String>
<sys:String>four</sys:String>
<sys:String>five</sys:String>
</x:Array>
</Page.Resources>
<StackPanel Width="200">
<ComboBox IsSynchronizedWithCurrentItem="False" Margin="25"
ItemsSource="{Binding Source={StaticResource myStrings}}" />
<ComboBox IsSynchronizedWithCurrentItem="False" Margin="25"
ItemsSource="{Binding Source={StaticResource myStrings}}" />
</StackPanel>
</Page>
You just need to set the IsSynchronizedWithCurrentItem property to false (by default it's null)
I'd guess (from the name of your binding) that the reason this is happening is that you're binding to a CollectionViewSource (that wraps a collection). This class is a proxy that WPF uses that includes (amongst other things) the selected item of a collection. Obviously if you're sharing this collection between two comboboxes, you're also sharing the selected item.
If you set ItemsSource to something that's not a CollectionViewSource, the control will automatically wrap it in one. So, my suggestion would be to bind directly to a collection instead of wrapping in a CollectionViewSource - or, alternatively, create two CollectionViewSource instances, one for each ComboBox.
You can separately bind the SelectedItem property for each combo box separately.
i.e.
SelectedItem={Binding SelectedItem1}
This way when each one's item gets set, it gets stored into a different place.
I have a silverlight combobox inside of a dataform as follows:
<dataControls:DataForm x:Name="newScheduleMasterForm" Height="350" Width="450" MinWidth="400"
VerticalAlignment="Top"
CommandButtonsVisibility="None"
Header="Add New Master Schedule"
HorizontalAlignment="Left" AutoGenerateFields="False" ContentLoaded="newScheduleMasterForm_ContentLoaded" >
<dataControls:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<dataControls:DataField>
<ComboBox x:Name="cbScheduleType" SelectedItem="{Binding Schedule, Mode=TwoWay}" SelectedIndex = "0"
ItemsSource="{Binding GetScheduleTypeValues, Source={StaticResource validDataSource}}"
/>
</dataControls:DataField>
</StackPanel>
</DataTemplate>
</dataControls:DataForm.EditTemplate>
</dataControls:DataForm>
The combobox cbScheduleType ItemsSource has values of "Interior" and Exterior. I am unable to display the default selected value "Interior" in the text box of the combobox. Is there a way to do it.
Thanks in advance
Mohit
The problem is that you're trying to both set SelectedIndex and bind Selected Item. I would just set Schedule to Interior in your constructor in codebehind and remove the SelectedIndex attribute.
There are some problems with data binding the SelectedItem property in the default Silverlight ComboBox control.
One way to work around this by creating a custom control that inherits from ComboBox and adds a SelectedValue dependency property.
There's a good example of this on Rockford Lhotka's blog at:
http://www.lhotka.net/weblog/SilverlightComboBoxControlAndDataBinding.aspx