I'm new to WPF. I have a combobox which when choosing a value three other fields (AbbrBlock, MultiBrandSupplier, IgnoreNoCompetition) update along to show the correct relevant values according to the data source. No problem with this.
Issue arises when I try to add to the combobox a custom value, although the combobox shows all values correctly, the other fields don't change when changing the combobox's value.
Here's the working code (without the additional custom combobox value - stripped to the key pieces):
<Window.Resources>
<local:OrdersDataSet x:Key="ordersDataSet" />
<CollectionViewSource x:Key="caSuppliersViewSource" Source="{Binding CaSuppliers, Source={StaticResource ordersDataSet}}"/>
</Window.Resources>
...
<StackPanel DataContext="{StaticResource caSuppliersViewSource}">
<ComboBox Name="SupplierDropdown" DisplayMemberPath="SupplierName"
ItemsSource="{Binding Source={StaticResource ResourceKey=caSuppliersViewSource}}"/>
<TextBlock Name="AbbrBlock" VerticalAlignment="Center" Text="{Binding Abbr}"/>
<CheckBox Name="MultiBrandSupplier" IsChecked="{Binding MultiBrand}"/>
<CheckBox Name="IgnoreNoCompetition" IsChecked="{Binding IgnoreNoCompetition}"/>
</StackPanel>
Here's the code with the added custom value which shows correctly but the other fields don't update when changing the combobox value:
<Window.Resources>
<local:OrdersDataSet x:Key="ordersDataSet" />
<CollectionViewSource x:Key="caSuppliersViewSource" Source="{Binding CaSuppliers, Source={StaticResource ordersDataSet}}"/>
</Window.Resources>
...
<StackPanel DataContext="{StaticResource caSuppliersViewSource}">
<StackPanel.Resources>
<CompositeCollection x:Key="myCompositeCollection">
<CollectionContainer Collection="{Binding Source={StaticResource ResourceKey=caSuppliersViewSource}}" />
<ComboBoxItem Content="Add New..." />
</CompositeCollection>
</StackPanel.Resources>
<ComboBox Name="SupplierDropdown" DisplayMemberPath="SupplierName"
ItemsSource="{Binding Source={StaticResource myCompositeCollection}}"/>
<TextBlock Name="AbbrBlock" VerticalAlignment="Center" Text="{Binding Abbr}"/>
<CheckBox Name="MultiBrandSupplier" IsChecked="{Binding MultiBrand}"/>
<CheckBox Name="IgnoreNoCompetition" IsChecked="{Binding IgnoreNoCompetition}"/>
</StackPanel>
What am I missing here?
Looks like the ComboBox was updating caSuppliersViewSource's View.CurrentItem property (I think) to match its SelectedItem in your first snippet. In the second, the CollectionViewSource is buried inside a CompositeCollection so that doesn't happen any more. However, the ComboBox is still selecting an item, and you can just bind to that using ElementName. No need for setting the DataContext on the StackPanel with this version.
<StackPanel>
<StackPanel.Resources>
<CompositeCollection x:Key="myCompositeCollection">
<CollectionContainer Collection="{Binding Source={StaticResource ResourceKey=caSuppliersViewSource}}" />
<ComboBoxItem Content="Add New..." />
</CompositeCollection>
</StackPanel.Resources>
<ComboBox
Name="SupplierDropdown"
DisplayMemberPath="SupplierName"
ItemsSource="{Binding Source={StaticResource myCompositeCollection}}"
/>
<TextBlock
Name="AbbrBlock"
VerticalAlignment="Center"
Text="{Binding SelectedItem.Abbr, ElementName=SupplierDropdown}"
/>
<CheckBox
Name="MultiBrandSupplier"
IsChecked="{Binding SelectedItem.MultiBrand, ElementName=SupplierDropdown}"
/>
<CheckBox
Name="IgnoreNoCompetition"
IsChecked="{Binding SelectedItem.IgnoreNoCompetition, ElementName=SupplierDropdown}"
/>
</StackPanel>
You could also give eyour viewmodel a SelectedDBItem property of the same type as whatever caSuppliersViewSource contains, and bind ComboBox.SelectedItem to that. Then you could do this:
<TextBlock
Name="AbbrBlock"
VerticalAlignment="Center"
Text="{Binding SelectedDBItem}"
/>
But that's six dozen of one, half of another, or something -- unless you want to do something else with SelectedDBItem in your viewmodel, then it's handy.
Related
I'm trying to populate a TreeView in a TestExplorerControl:
I never had to use a CollectionViewSource until now, so I used this tutorial to get a grasp of how to group my ObservableCollection<TestMethod> in XAML, and use that grouping for my tree view - I implemented the data templates in the <UserControl.Resources>, because I want the flexibility to eventually allow the user change the way tests are regrouped:
<CollectionViewSource x:Key="OutcomeGroupViewSource" Source="{Binding Model.Tests}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Result.Outcome" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate x:Key="TestMethodTemplate" DataType="{x:Type local:TestMethod}">
<StackPanel Orientation="Horizontal">
<Image .../>
<TextBlock .../>
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="OutcomeTemplate" DataType="{x:Type CollectionViewGroup}"
ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource TestMethodTemplate}">
<StackPanel Orientation="Horizontal">
<Image ../>
<TextBlock ../>
<TextBlock ../>
</StackPanel>
</HierarchicalDataTemplate>
Then I have this markup for the actual <TreeView>:
<TreeView Grid.Row="2"
ItemsSource="{Binding Source={StaticResource OutcomeGroupViewSource}, Path=View.Groups}"
ItemTemplate="{StaticResource OutcomeTemplate}" />
What's wrong with this markup, for the TreeView to fail updating? The ViewModel clearly has all the data I need to display (the breakpoint that was hit is on the current line, in yellow):
Found it. It's the Path of the ItemsSource binding in the tree view:
ItemsSource="{Binding Source={StaticResource OutcomeGroupViewSource}, Path=View.Groups}"
Path=View.Groups satisfies IntelliSense, but is wrong.
It needs to be Path=Groups, even if the designer supposedly can't resolve the property:
I have a WPF usercontrol with a combobox & textbox. I want the textbox to hold the value of the selected item in the combobox and it works fine if I use SelectedValue in the binding path. However if I try to use the Title column of the combobox (SelectedValue.Title) the value of the textbox is overwritten but nothing is displayed. Can anyone tell me what I am doing wrong? My code sample is below. I am a newbie at this so please be kind :)
<ComboBox x:Name="ComboProject" Grid.Column="4" Grid.Row="0" TabIndex="14"
ItemsSource="{Binding Source={StaticResource Projects}, XPath=./Project}"
SelectedValuePath="#Item"
Tag="Project Number"
TextSearch.TextPath="#Item">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text= "{Binding XPath= #Item}" Width="90" />
<TextBlock Text= "{Binding XPath= #Title}" Width="220" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox x:Name="loaded" Text="{Binding Path=SelectedValue.Title, NotifyOnSourceUpdated=True, ElementName=ComboProject}" Grid.Row="2" Grid.Column="4" Tag="Project Title" TabIndex="15"/>
You set SelectedValuePath="#Item", so that's what SelectedValue has right now. Try setting it to Title and binding directly to SelectedValue:
<ComboBox x:Name="ComboProject"
ItemsSource="{Binding Source={StaticResource Projects}, XPath=./Project}"
SelectedValuePath="#Title">
<ComboBox.ItemTemplate>
...
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Text="{Binding SelectedValue, ElementName=ComboProject}" />
I removed some other code for clarity of example.
EDIT :
Ok, if you want to use SelectedValue for other purposes we can bind TextBox to SelectedItem instead. If the Title is an attribute of a selected XML node, then we can access it like this:
<TextBox Text="{Binding SelectedItem.Attributes[Title].Value, Mode=OneWay, ElementName=ComboProject}" />
I have a multiselect combobox that works fine. Except for the text. I want it to always have the same text ("Commodity Filter") regardless of what the user has selected.
If I set iseditable to true and the text to CommodityFilter it looks fine until the user makes a selection, then it is garbage (displays object type name). How can I hard code some text there? (Actually ideally i would databind it so it can change depending on whether anything is selected, but that would be a bonus at this point)
<ComboBox IsEditable="True" Text ="Commodity Filter" ItemsSource="{Binding Path=ActiveCommodities}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}"
Width="20" />
<TextBlock Text="{Binding Commodity}"
Width="100" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I ended up creating a custom object for populating the ComboBox (which had the IsSelected property and implemented INotifyPropertyChanged) because I was creating several comboboxes to control filtering. Once I did this is was trivial to override the tostring on the customobject and pass in the appropriate text. So the xaml did not change much.
I would have preferred to overlay with a text box but that seemed to be beyond my abilities to get a polished look in a reasonable time.
<ComboBox ItemsSource="{Binding Path=ActiveFuturesMonths}"
IsEditable="True"
IsReadOnly="True"
Text="Futures Month Filter" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}"
Width="20" />
<TextBlock Text="{Binding Text}"
Width="100" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Actually the crux is in setting -
IsEditable="True"
IsReadOnly="True"
Text="Futures Month Filter"
rather than creating custom object. thanks a lot it helped.
I have a datagrid nested inside the ItemTemplate of a ListBox. I'm trying to display a tree like data structure using this. My classes are as follows.
The object in my data context contains a List<Section> named Sections, my ListBox is bound to this. Each Section contains a List<Item> named Items, the DataGrid in eac ItemTemplate is bound to this.
When I run the app, I get a null reference exception from the XAML at the line with the binding. Is there a better/alternative way of doing this, or am I missing a trick with the binding?
<Window.Resources>
<CollectionViewSource x:Key="SectionSource" /><!-- this is initialized and filled with an ObservableCollection<Section> Sections when the window loads-->
</Window.Resources>
<ListBox x:Name="lstIngredients" ItemsSource="{Binding Source={StaticResource SectionSource}}">
<ListBox.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<CollectionViewSource x:Key="itemsSource" Source="{Binding Items}"/>
</DataTemplate.Resources>
<DataGrid x:Name="dgItems" IsReadOnly="false" AutoGenerateColumns="False" SelectionMode="Single" SelectionUnit="FullRow" IsSynchronizedWithCurrentItem="True"
DataContext="{Binding}"
ItemsSource="{Binding Source={StaticResource Items}}"
EnableRowVirtualization="false"
VirtualizingStackPanel.VirtualizationMode="Standard"
<DataGrid.Columns>
<DataGridTemplateColumn Width="2*" Header="{lex:LocText ChickenPing.Shared:Strings:Measurement}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="quantity" Text="{Binding Measurement}" TextTrimming="CharacterEllipsis" TextAlignment="Left"/>
<!-- Null reference on this line caused by the binding. If I set this to any DependencyProperty on an Item object, I get a null reference-->
</DataTemplate>
This need to be path
ItemsSource="{Binding Source={StaticResource Items}}"
ItemsSource="{Binding Path=PropertyThatIsCollection}"
And delete the DataContext line
I eventually tracked this down to an event which was set in one of the TemplateColumns. Switching the event from
<TextBlock x:Name="quantity" Text="{Binding Measurement}" GotFocus="txt_GotFocus" />
to
<Style x:Key="FocusableTextbox" TargetType="{x:Type TextBox}">
<EventSetter Event="GotFocus" Handler="txt_GotFocus" />
</Style>
...
<TextBlock x:Name="quantity" Text="{Binding Measurement}" Style={StaticResource FocusableTextbox} />
In the XAML below the ToolTip correctly binds to RelativeSource Self. However, I can't for the life of me work out how to get the TextBlock in the commented block to refer to SelectedItem.Description
<Controls:RadComboBoxWithCommand x:Name="cmbPacking"
Grid.Row="2"
Grid.Column="5"
ItemsSource="{Binding PackingComboSource}"
DisplayMemberPath="DisplayMember"
SelectedValuePath="SelectedValue"
SelectedValue="{Binding ElementName=dataGrid1, Path=SelectedItem.PackingID}"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem.Description}"
IsSynchronizedWithCurrentItem="True"
Style="{StaticResource comboBox}">
<!-- <Controls:RadComboBoxWithCommand.ToolTip>-->
<!-- <TextBlock Text="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem.Description}" TextWrapping="Wrap" Width="50"/>-->
<!-- </Controls:RadComboBoxWithCommand.ToolTip>-->
</Controls:RadComboBoxWithCommand>
I would appreciate any suggestions
Thanks - Jeremy
It seems that since ToolTip does not have a parent, you need to bind to the placement target as below:
<Controls:RadComboBoxWithCommand Grid.Row="2"
Grid.Column="5"
ItemsSource="{Binding PackingComboSource}"
DisplayMemberPath="DisplayMember"
SelectedValuePath="SelectedValue"
SelectedValue="{Binding ElementName=dataGrid1, Path=SelectedItem.PackingID}"
IsSynchronizedWithCurrentItem="True"
Style="{StaticResource comboBox}">
<Controls:RadComboBoxWithCommand.ToolTip>
<ToolTip DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget}">
<TextBlock Text="{Binding SelectedItem.Description}"
TextWrapping="Wrap"
Width="100" />
</ToolTip>
</Controls:RadComboBoxWithCommand.ToolTip>
</Controls:RadComboBoxWithCommand>
Hope this is useful for someone
Jeremy
A relative source of self means the current object, which would be the TextBox itself in this particular case. You want a relative source of find ancestor with an ancestor type of RadComboBoxWithCommand. Alternatively, and perhaps a little simpler, is to give the combo box a name and use ElementName in your binding instead of a relative source:
<ComboBox x:Name="cb" ...>
<ComboBox.ToolTip>
<TextBlock Text="{Binding SelectedItem.Description, ElementName=cb}" .../>