I already search widely, but I can't find any solution to my case.
I have several ComboBox's at my project and I was searching for an AutoComplete solution, then I found a good one and applied in my project, I applied solution's style as well to all ComboBox in my project.
After that, SelectedItem stopped working, some one can help me ?
My Combobox:
<ComboBox Name="CbOwnerType" Grid.Column="1" Grid.Row="2" ItemsSource="{Binding Path=OwnerTypes, Mode=OneWay}" SelectedItem="{Binding Owner.OwnerType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="Id" DisplayMemberPath="Name" Margin="5,0,10,0" />
My style:
<Style TargetType="{x:Type ComboBox}">
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontWeight" Value="ExtraBold" />
<Setter Property="IsEditable" Value="False"/>
<Setter Property="IsSynchronizedWithCurrentItem" Value="False" />
<Setter Property="StaysOpenOnEdit" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid>
<ToggleButton Name="ToggleButton" Template="{StaticResource ComboBoxToggleButton}" Grid.Column="2" Focusable="True" IsChecked="{Binding Path=IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" ClickMode="Press" BorderThickness="0" />
<ContentPresenter Name="ContentSite" IsHitTestVisible="False" Content="{TemplateBinding SelectionBoxItem}" ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" Margin="5,0,20,0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBox x:Name="PART_EditableTextBox" Style="{x:Null}" Template="{StaticResource ComboBoxTextBox}" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="3,3,23,3" Focusable="True" Background="Transparent" Visibility="Hidden" IsReadOnly="{TemplateBinding IsReadOnly}" />
<Popup Name="Popup" Placement="Bottom" IsOpen="{TemplateBinding IsDropDownOpen}" AllowsTransparency="True" Focusable="False" PopupAnimation="Slide">
<Themes:SystemDropShadowChrome Margin="4,6,4,6" CornerRadius="4">
<Grid Name="DropDown" SnapsToDevicePixels="True" MinWidth="{TemplateBinding ActualWidth}" MaxHeight="{TemplateBinding MaxDropDownHeight}">
<Border x:Name="DropDownBorder" Background="{StaticResource WindowBackgroundBrush}" BorderThickness="1" BorderBrush="{StaticResource SolidBorderBrush}" />
<ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True">
<ItemsPresenter />
</ScrollViewer>
</Grid>
</Themes:SystemDropShadowChrome>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasItems" Value="false">
<Setter TargetName="DropDownBorder" Property="MinHeight" Value="95"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
</Trigger>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
<Trigger SourceName="Popup" Property="Popup.AllowsTransparency" Value="true">
<Setter TargetName="DropDownBorder" Property="CornerRadius" Value="4"/>
<Setter TargetName="DropDownBorder" Property="Margin" Value="0,2,0,0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
UPDATE
My ToggleButton
<ControlTemplate x:Key="ComboBoxToggleButton" TargetType="{x:Type ToggleButton}" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Border x:Name="Border" Grid.ColumnSpan="2" BorderBrush="{StaticResource LabPetsStandardColor}" BorderThickness="1" CornerRadius="5" />
<Border Grid.Column="0" Margin="1" Background="Transparent" BorderBrush="{StaticResource NormalBorderBrush}" BorderThickness="0" CornerRadius="5,0,0,5" />
<Path x:Name="Arrow" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" Data="M 0 0 L 4 4 L 8 0 Z">
<Path.Fill>
<SolidColorBrush Color="Black" />
</Path.Fill>
</Path>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ToggleButton.IsMouseOver" Value="true">
<Setter TargetName="Border" Property="Background" Value="{StaticResource LabPetsStandardColor}" />
</Trigger>
<Trigger Property="ToggleButton.IsChecked" Value="true">
<Setter TargetName="Border" Property="Background" Value="{StaticResource LabPetsPressedStandardColor}" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Border" Property="Background" Value="{StaticResource DisabledBackgroundBrush}" />
<Setter TargetName="Border" Property="BorderBrush" Value="{StaticResource DisabledBorderBrush}" />
<Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
<Setter TargetName="Arrow" Property="Fill" Value="{StaticResource DisabledForegroundBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
My TextBox
<Style x:Key="ComboBoxTextBox" TargetType="{x:Type TextBox}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="AllowDrop" Value="True" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" Background="#00FFFFFF" Name="PART_ContentHost" Focusable="False" VerticalAlignment="Center" VerticalContentAlignment="Center" Margin="0">
<ScrollViewer.Style>
<Style TargetType="ScrollViewer">
<Setter Property="OverridesDefaultStyle" Value="True" />
</Style>
</ScrollViewer.Style>
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Some, please, can help me ?
UPDATE 2
Found a hack, not the perfect solution, but kind of works…
If I insert property SelectedValue
and the value Owner.OwnerTypeId
, it works like a charm… But, it's right this?
My Combobox now:
<ComboBox Name="CbOwnerType" Grid.Column="1" Grid.Row="2" ItemsSource="{Binding Path=OwnerTypes, Mode=OneWay}" SelectedItem="{Binding Owner.OwnerType}" SelectedValue="{Binding Owner.OwnerTypeId}" SelectedValuePath="Id" DisplayMemberPath="Name" Margin="5,0,10,0" />
This is a solution, not that I like it, but it's a solution…
Some one can answer why SelectedItem
isn't working as it should ?
Obs.: When I change the selection, SelectedItem
works, just doesn't work when I load my view.
UPDATE 3
Ok, it's worked like I said, but the problem is that WPF is hitting 4 times at my ViewModel, so I changed my ComboBox
a little:
<ComboBox Name="CbOwnerType" Grid.Column="1" Grid.Row="2" ItemsSource="{Binding Path=OwnerTypes, Mode=OneWay}" SelectedItem="{Binding Owner.OwnerType}" SelectedValue="{Binding Owner.OwnerTypeId, Mode=OneTime}" SelectedValuePath="Id" DisplayMemberPath="Name" Margin="5,0,10,0" />
So, now, WPF just search the OwnerTypeId
and when I change an item, WPF just hits 2 times.
UPDATE 4
Ok, another strange finding…
In another ComboBox, with the same properties, except SelectedValue
, it's working perfect… I can't understand what is happening.
UPDATE 5
Sorry about that, I forgot to post my models.
Model Owner:
public class Owner
{
public int Id { get; set; }
public int OwnerTypeId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string FormatedPhone
{
get
{
if (this.Phone == null)
return string.Empty;
switch (this.Phone.Length)
{
case 11:
return Regex.Replace(this.Phone, @"(\d{2})(\d{4})(\d{4})", "($1) $2-$3");
case 12:
return Regex.Replace(this.Phone, @"(\d{2})(\d{5})(\d{4})", "($1) $2-$3");
default:
return this.Phone;
}
}
}
public string Phone { get; set; }
public string CellPhone { get; set; }
public string FormatedCellPhone
{
get
{
if (this.CellPhone == null)
return string.Empty;
switch (this.CellPhone.Length)
{
case 11:
return Regex.Replace(this.Phone, @"(\d{2})(\d{4})(\d{4})", "($1) $2-$3");
case 12:
return Regex.Replace(this.Phone, @"(\d{2})(\d{5})(\d{4})", "($1) $2-$3");
default:
return this.CellPhone;
}
}
}
public string Email { get; set; }
public virtual OwnerType OwnerType { get; set; }
public virtual ICollection<Animal> Animals { get; set; }
public Owner()
{
this.OwnerType = new OwnerType();
this.Animals = new List<Animal>();
this.ErrorList = new StringBuilder();
}
Model OwnerType:
public class OwnerType
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Owner> Owners { get; set; }
public OwnerType()
{
this.Owners = new List<Owner>();
}
}
Best Answer
Your
SelectedItem
binding isn't working because WPF compares theSelectedItem
to the items in theItemsSource
by the.Equals()
method, which by default compares by reference. And the instance in memory containing yourSelectedItem
is not the same instance in memory as one of the items in yourItemsSource
There are 3 ways to handle this.
First, as you've already discovered you can bind
SelectedValue
to a value type property on your item, and setSelectedValuePath
.This is usually the solution I go with because it's often easiest
Second, you could ensure your
SelectedItem
is set to the same reference in memory as one of theItemsSource
items. Depending on the application design, this isn't a bad choice either.And last of all, you could override the
.Equals()
method on theOwnerType
object so it considers the two values equal if theId
properties are the same.I usually try to avoid this method unless I know I'll always want to compare this object's equality by the
Id
property only, but sometimes this is the best way to go.Also, its good practice to override
.GetHashCode()
whenever you overwrite.Equals()
.As a side note, you usually don't want to be binding both
SelectedItem
andSelectedValue
. They are two different ways of setting the same thing, and you can get unexpected results by binding both of them.