Xaml - combo box - why does selectedValue NOT work with multiple columns? - wpf

Hi and thanks for your help.
The following xaml works just fine:
<ComboBox Name="cboCit_type"
IsSynchronizedWithCurrentItem="True"
mvvm:View.FlowsWithPrevious="True"
ItemsSource="{Binding Path=cuCodeInfo.CitTypes}"
SelectedValuePath="code"
DisplayMemberPath="code"
Text="{Binding cit_type}"
IsEditable="true"
IsReadOnly="false"
SelectedValue="{Binding Path=cit_type}">
</ComboBox>
cuCodeInfo.CitTypes is simply a list of items that are available. There are a number of public properties, but the 2 in question are "code" and "description".
Right now, I show the available code values and the user selects one. If one was already selected, then it shows when the page is displayed. This is all good.
So then I thought it might be nice to show both the code and the description. I figured it shouldn't be too hard...
So I removed the DisplayMemberPath statement and added in an ItemTemplate.
When I did so everything looked great until I tried to select an item from the list. When I did so, instead of showing the selected code, I would get an empty string. I have searched the internet trying to find the one thing I need to add to the DataTemplate to fix this, but everything I have tried has failed. Here is the code that is NOT working:
<ComboBox Name="cboCit_type"
IsSynchronizedWithCurrentItem="True"
mvvm:View.FlowsWithPrevious="True"
ItemsSource="{Binding Path=cuCodeInfo.CitationTypes}"
SelectedValuePath="code"
Text="{Binding cit_type}"
IsEditable="true"
IsReadOnly="false"
SelectedValue="{Binding cit_type}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Border BorderThickness="0,0,1,0" BorderBrush="Black">
<TextBlock Text="{Binding Path=code}" mvvm:View.WidthEx="2" ></TextBlock>
</Border>
<TextBlock Text="{Binding Path=description}" mvvm:View.WidthEx="15" Margin="1" ></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Any help will be greatly appreciated.
As an aside, I have to use this exact same format (same list, etc. just different SelectedValue) in a number of forms - so if you want to suggest the best way to do this in xaml, that would be nice. In my pre-xaml days, I would just create a control, set a property or 2, and use that in all my forms. But I am supposed to use xaml, so am not sure of the best way to do this.
Thanks!

I can't believe that I searched all over the net to find an answer and, until now, could not find one. The answer is as easy as I thought it should be.
Just replace:
DisplayMemberPath="code"
with
TextSearch.TextPath="code"
and the code works just fine.
Thanks to all who helped.

I can give you an idea how to use SelectedItem
ViewModel
public class ViewModel
{
public ViewModel()
{
//Suppose your collection CitTypes is Initialized and filled with there Items
//Now you can set first Element as selected in ComboBox
SelectedItem = CitTypes.FirstOrDefault();
}
CitType selectedItem;
public CitType SelectedItem
{
get { return selectedItem; }
set { selectedItem = value; RaisePropertyChanged("SelectedItem"); }
}
}
xaml
<ComboBox Name="cboCit_type"
IsSynchronizedWithCurrentItem="True"
mvvm:View.FlowsWithPrevious="True"
ItemsSource="{Binding Path=cuCodeInfo.CitationTypes}"
Text="{Binding cit_type}"
IsEditable="true"
IsReadOnly="false"
**SelectedItem="{Binding SelectedItem}"**>

Related

WPF Combobox display entity name in dropdown instead of DisplayMemberPath

I have the following combobox:
<ComboBox Name="cbBonusType"
DisplayMemberPath="BonusTypeName"
SelectedValuePath="ID" Width="150" Margin="10,0,0,0"
SelectionChanged="cbBonusType_SelectionChanged"/>
When running:
When selecting an item, the combobox shows exactly the right string.
But while the droppbox is open, the named displayed in the droppbox are all set to the name of the entity: "CaSaMa.WPF.UI.Competiotion.BonusType".
Why is that and how do I fix it?
Best bet would be override the ToString on your object - that would give you a consistent display across your application.
hope that helps!
Just re-read your question - think I got the wrong end of the stick.. What you can do is set the ItemTemplate on the control instead like this;
<ComboBox Name="cbBonusType"
ItemTemplate="{StaticResource DisplayTemplate}"
SelectedValuePath="ID" Width="150" Margin="10,0,0,0"
SelectionChanged="cbBonusType_SelectionChanged"/>
then create the template like this
<DataTemplate x:Key="DisplayTemplate"
DataType="{x:Type <YOURTYPE>}">
<TextBlock Text="{Binding BonusTypeName}"/>
</DataTemplate>
its a known problem and this is the workaround sadly!
hope it helps.. this time! :)
edit : updated code just incase anyone else would like to use it!
ste.
Its way easier than this...in XAML:
<ComboBoxItem Content="This Value" Tag="This Value"/>
Then in code behind:
GetValue=ComboBoxName.selecteditem.tag.tostring()
GetValue will be "This Value" instead of
"System.Windows.Controls.ComboBoxItem: This Value"

ListBox bound to collection does not automatically refresh. Why?

I have a ListBox which is bound to a collection. When I add an item to the collection I see no change in the ListBox.
However, when I resize the window a little, then the new item suddenly appears in the ListBox. So the binding seems to be working, just the refresh is missing.
What might I be doing wrong here?
XAML:
<ListBox Grid.Row="2" Grid.Column="1" Name="TestModules" ItemsSource="{Binding ModuleList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding TE}"/>
<TextBlock Text="-"/>
<TextBlock Text="{Binding AF}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code:
private List<PruefModule> _moduleList = new List<PruefModule>();
public ICollectionView ModuleList { get; private set; }
ModuleList = CollectionViewSource.GetDefaultView(_moduleList);
_moduleList.Add((PruefModule)ModulesGrid.SelectedItem);
You should use an ObservableCollection instead of the ICollectionView and it should work ok.
From MSDN:
Represents a dynamic data collection that provides notifications when
items get added, removed, or when the whole list is refreshed.
like Adrian said you should use ObservableCollection.
nevertheless you can call Refresh after adding a item.
_moduleList.Add((PruefModule)ModulesGrid.SelectedItem);
ModuleList.Refresh();

WPF DataGrid, in a Binding in a ColumnTemplate access a Element outside of the DataGrid

I'd like to access a Object from my UserControl from within the Datgrids ColumnTemplate.
This doesn't work. Now I've read it's because of the Datacontext.
I found this Example, which should fix this: http://blog.errorok.com/2010/09/09/212/
But the Event: ColumnDataContextChanged is never called in my Project!
here's a part of my XAML:
<DataGridTemplateColumn Header="Database-Fieldtype" Width="Auto" IsReadOnly="False" SortMemberPath="DatabaseFieldType">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DatabaseFieldType}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ConfigurationTool:EditProtocolDatasets}}, Path=grdDatasets.SelectedItem.Storage.DatabaseFieldTypes}"
SelectedItem="{Binding DatabaseFieldType}"
VerticalAlignment="Top" Width="179" Padding="0" Margin="0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
ConfigurationTool:EditProtocolDatasets is my UserControl, grdDatasets is another Datagrid, to which SelectedItem I'd like to bind!
Okay, I'm going to suggest a completely different direction than my first one. My guess is that you have the ItemsSource for grdDatasets bound to something.
For the item that's going to act as your datacontext for the control, make sure it has the following characteristics, or at least a comparable structure:
public class ListOfDataSets : DependencyObject
{
public IEnumerable<DataSetOption> Items
{
get
{
...Whatever you normally use to get your DataSetOptions...
}
}
public DataSetOption SelectedItem
{
get { return (DataSetOption)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(DataSetOption), typeof(ListOfDataSets), new PropertyMetadata(null));
}
The key here is that you have one property that is a list of your choices, and another property that represents one of those items.
Then, in your XAML, your control can have the following structure:
<UserControl>
<UserControl.Resources>
<ConfigurationTool:ListOfDatasets x:Key=DataSetOptions />
</UserControl.Resources>
<StackPanel Name="LayoutRoot">
<DataGrid Name="grdDatasets"
ItemsSource="{Binding Source={StaticResource DataSetOptions}, Path=Items}"
SelectedItem="{Binding Source={StaticResource DataSetOptions}, Path=SelectedItem}"
...
</DataGrid>
...
<DataGrid Name="OtherDataGrid" ItemsSource="{Binding OtherSource}">
<DataGrid.Columns>
...
<DataGridTemplateColumn Header="Database-Fieldtype" Width="Auto" IsReadOnly="False" SortMemberPath="DatabaseFieldType">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DatabaseFieldType}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{StaticResource DataSetOptions}, Path=SelectedItem.Storage.DatabaseFieldTypes}" SelectedItem="{Binding DatabaseFieldType, Mode=TwoWay}"
VerticalAlignment="Top" Width="179" Padding="0" Margin="0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</Datagrid.Columns>
</DataGrid>
</StackPanel>
</UserControl>
I actually tried this structure out, and the databinding works fine. If the DataSetOptions change a lot, though, this solution may not work, unless you're using MVVM, and the ViewModel is good at tracking what options are available, and presents them properly to the View.
Hopefully this makes sense. I actually tried this one before answering.
I was not correct with my original answer, and overestimated the capabilities of RelativeSource before I experimented with it.
---Original text below---
I think I can help, but I need a few more details. For now I'm just going to work off assumptions.
Assumption 1: You're in a WPF UserControl with a DataGrid that has a defined ItemsSource.
Assumption 2: The UserControl has another element that you want a column within your DataGrid to have access to.
If these two assumptions are correct, it is a much better problem to have in WPF than in Silverlight.
Each row in your DataGrid is going to be working from within a DataContext that consists of the Item for that row. But, you can reach outside of the cell's (or any) DataContext with a RelativeSource.
So, if you wanted to go up the Visual Tree to get to your control's Width, you would use:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MyUserControl}}, Path=Width}
This will trace upward in the Visual Tree until an object of type "MyUserControl" is found, at which point it will grab the "Width" property and bind to it.
The Path doesn't have to be only one item deep, either. You can run up an down your visual tree as required. As this gets more complex, though, your code is going to be more fragile.
If this isn't correct, please post your XAML (or something similar) and say so, and I'll spin up a test environment and edit my post.

WPF: ComboBox with selecteditem set make not use of SelectedIndex=0?

Why is the first element in my combobox popup menu not shown in the selected item area of
my combobox , when I use the SelectedItem binding? Without that it is showing up ?? Using
the same code selecteditem + selectedindex that is no problem!
<ComboBox
ItemsSource="{Binding SchoolclassSubjectViewModels}"
SelectedItem="{Binding SelectedSchoolclassSubjectViewModel}"
SelectedIndex="0"
Height="23"
HorizontalAlignment="Left"
Margin="375,13,0,0"
VerticalAlignment="Top"
Width="151">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SchoolclassName}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding SubjectName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Well as workaround I used:
SchoolclassSubjectViewModels.Add(schoolclassSubjectVM);
SelectedSchoolclassSubjectViewModel = schoolclassSubjectVM;
and this:
SelectedItem="{Binding SelectedSchoolclassSubjectViewModel,Mode=TwoWay}"
but I would prefer the xaml only way as it should really work.
It is because the reference inside your ItemsSource collection is not the same as the one in your SelectedItem property. I would venture to guess that you are using one object context to query your database for the list of SchoolclassSubject objects which the ItemsSource is bound to, but another context to query the actual data item to which you bind the SelectedItem. Even though the list contains a reference which represents the value held by your object, it is not really the same reference, but a separate instance of the same data.
There are ways to solve this issue, most of them involve using the SelectedValuePath and SelectedValue instead of the SelectedItem properties, but the concrete solution would be different depending on your particular ORM.

WPF Combobox DisplayMemberPath

Ok, I looked at other questions and didn't seem to get my answer so hopefully someone here can.
Very simple question why does the DisplayMemberPath property not bind to the item?
<ComboBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding PromptList}" DisplayMemberPath="{Binding Name}" SelectedItem="{Binding Prompt}"/>
The trace output shows that it is trying to bind to the class holding the IEnumerable not the actual item in the IEnumerable. I'm confused as to a simple way to fill a combobox without adding a bunch a lines in xaml.
It simply calls the ToString() for the object in itemssource. I have a work around which is this:
<ComboBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding PromptList}" SelectedItem="{Binding Prompt}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
But in my opinion it's too much for such a simple task. Can I use a relativesource binding?
DisplayMemberPath specifies the path to the display string property for each item. In your case, you'd set it to "Name", not "{Binding Name}".
You are not binding to the data in the class, you are telling it to get it's data from the class member that is named by the member "name" so, if your instance has item.Name == "steve" it is trying to get the data from item.steve.
For this to work, you should remove the binding from the MemberPath. Change it to MemberPath = "Name" this tells it to get the data from the member "Name". That way it will call item.Name, not item.steve.
You should change the MemberPath="{Binding Name}" to MemberPath="Name". Then it will work.
You could remove DisplayMemberPath and then set the path in the TextBlock.
The DisplayMemberPath is really for when you have no ItemTemplate.
Or you could remove your ItemTemplate and use DisplayMemberPath - in which case it basically creates a TextBlock for you.
Not recomended you do both.
<TextBlock text="{Binding Path=Name, Mode=OneWay}"
Alternatively you don't need to set the DisplayMemberPath. you can just include an override ToString() in your object that is in your PromptList. like this:
class Prompt {
public string Name = "";
public string Value = "";
public override string ToString() {
return Name;
}
}
The ToString() will automatically be called and display the Name parameter from your class. this works for ComboBoxes, ListBoxes, etc.
Trying this :
<ComboBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding PromptList}" SelectedItem="{Binding Prompt}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
from what i can figure,
"DisplayMemberPath" uses reflection to get the property name in the data context class, if it cant find it nothing will be displayed.
if class
class some_class{
string xxx{ get; }
}
DisplayMemberPath=xxx, will show whatever value "xxx" is
if you want to concatenate properties from the datacontext you need to create an item template, which will show up in the header and the drop down list.
<ComboBox.ItemTemplate>
<DataTemplate DataType="employee">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding first_name}" />
<TextBlock Text="" />
<TextBlock Text="{Binding last_name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
you cannot have "DisplayMemberPath" and "ComboBox.ItemTemplate" set at the same time.

Resources