WPF is optional for ComboBox SelectedItem

I am already looking widely, but I can not find a solution for my business.

I have several ComboBox in my project, and I was looking for an AutoComplete solution, then I found a good one and applied it in my project, I also applied the solution style to all ComboBox in my project.

After that, SelectedItem stops working, can someone 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> 

Can some please help me?

UPDATE 2

Found a hack, not an ideal solution, but kind of work ...

If I insert the SelectedValue property and the Owner.OwnerTypeId value, it works like a charm ... But is that right?

My Combobox is 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 solution, not that I like it, but this solution ... Someone can answer why SelectedItem does not work as it should?

Ob .: When I change the selection, SelectedItem works, just does not work, when I load my view.

UPDATE 3

Ok, it worked, as I said, but the problem is that WPF gets to my ViewModel four times, so I changed the ComboBox bit:

 <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 is just looking for OwnerTypeId , and when I change the item, WPF just clicks 2 times.

UPDATE 4

Ok, one more strange discovery ... In another ComboBox with the same properties, except SelectedValue , it works perfectly ... I can not understand what is happening.

UPDATE 5

Sorry, I forgot to publish 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>(); } } 
+7
c # styles wpf combobox selecteditem
source share
3 answers

Binding SelectedItem does not work because WPF compares SelectedItem with items in ItemsSource using the .Equals() method, which compares by reference by default. And the in-memory instance containing your SelectedItem is not the same in-memory instance as one of the items in your ItemsSource

There are three ways to handle this.

  • First, as you have already discovered, you can bind SelectedValue to a value type property in your element and set SelectedValuePath .

     <ComboBox ItemsSource="{Binding Path=OwnerTypes}" SelectedValue="{Binding Owner.OwnerTypeId}" SelectedValuePath="Id" DisplayMemberPath="Name" /> 

    This is usually the solution I go with, because it is often easier

  • Secondly, you can make sure that your SelectedItem set to the same link in memory as one of the ItemsSource elements. Depending on the design of the application, this is also not a bad choice.

     public class Owner() { public int OwnerTypeId { get; set; } public OwnerType OwnerType { get { return StaticClass.OwnerTypes .FirstOrDefault(p => p.Id == this.OwnerTypeId); } set { if (value != null) OwnerTypeId = value.Id; } } } 
  • And finally, you can override the .Equals() method for the .Equals() object OwnerType that it considers two values ​​equal if the Id properties match.

     public override bool Equals(object obj) { if (obj == null || !(obj is OwnerType)) return false; return ((OwnerType)obj).Id == this.Id); } 

    Usually I try to avoid this method if I don’t know that I will always compare this equality of the object only using the Id property, but sometimes this is the best way.

    Also, its good practice to override .GetHashCode() whenever you overwrite .Equals() .

As a side note, you usually don't want to bind both SelectedItem and SelectedValue . These are two different ways of installing the same thing, and you can get unexpected results by linking them both.

+4
source share

You need to bind to the selectedValue template from comobox. Since you redefined the control pattern and placed your own TextBlock to display the selected value. Now that the value is selected from the user interface control, it takes care of displaying it. but it does not set the value to SelectedValue or SelectedItem.

 <TextBox x:Name="PART_EditableTextBox" Style="{x:Null}" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="3,3,23,3" Focusable="True" Background="Transparent" Visibility="Hidden" IsReadOnly="{TemplateBinding IsReadOnly}" Text="{TemplateBinding SelectedValue}"/> 

Hope this helps you

+1
source share

Look at the bindings and add up what they say:

 <ComboBox ItemsSource="{Binding Path=OwnerTypes, Mode=OneWay}" SelectedItem="{Binding Owner.OwnerType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="Id" DisplayMemberPath="Name" /> 

So this means:

  • Items in the list are retrieved from the OwnerTypes property.
  • The selected item will be bound to the OwnerType property within the instance of the Owner property.

Since there is no binding to SelectedValue , SelectedValuePath will be completely ignored. Pay attention to the definition of SelectedValuePath :

Gets or sets the path that is used to get SelectedValue from SelectedItem.

So, omit # 3. Working with C # 1 and # 2, suppose that the OwnerTypes property is of type List<OwnerTypeDef> . This means that the OwnerTypeDef type is the same type as the OwnerType property inside the Owner property. With this setting -

 public class OwnerDef : INotifyPropertyChanged // TODO implement INotifyPropertyChanged { public OwnerTypeDef OwnerType { get { return _ownerType; } set { if (_ownerType == value) return; _ownerType = value; RaisePropertyChanged(); } } private OwnerTypeDef _ownerType; } public class OwnerTypeDef : INotifyPropertyChanged { public string Name { get { return _name; } set { if (_name == value) return; _name = value; RaisePropertyChanged(); } } private string _name; public int Id { get { return _id; } set { if (_id == value) return; _id = value; RaisePropertyChanged(); } } private int _id; } public class ViewModel : INotifyPropertyChanged { public List<OwnerTypeDef> OwnerTypes { get { return _ownerTypes; } set { _ownerTypes = value; } } private List<OwnerTypeDef> _ownerTypes = new List<OwnerTypeDef> { new OwnerTypeDef { Name = "foo", Id = 1, }, new OwnerTypeDef { Name = "bar", Id = 2, }, new OwnerTypeDef { Name = "baz", Id = 3, }, }; public OwnerDef Owner { get { return _owner; } set { if (_owner == value) return; _owner = value; RaisePropertyChanged(); } } private OwnerDef _owner = new OwnerDef(); } 

- binding works for me. The "OwnerType" property inside the "Owner" is updated when the selection in the user interface changes.

Edit

Look at another scenario where you are using SelectedValuePath and SelectedValue . In this case, we bind the selected value to "Owner.OwnerTypeId" (integer). We will use SelectedValuePath=Id , which tells the structure to look for the "Id" property in the selected element (which is OwnerDef.Id , int ). Therefore, we need to add the corresponding int property to the OwnerDef class, name it "OwnerTypeId". XAML in this case will be:

 <ComboBox ItemsSource="{Binding Path=OwnerTypes, Mode=OneWay}" SelectedValue="{Binding Owner.OwnerTypeId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="Id" DisplayMemberPath="Name" /> 

With this setting, the binding correctly updates the "OwnerTypeId".

Edit # 2

It is also possible to use both SelectedItem and SelectedValue to update the OwnerType and OwnerTypeID properties. Be sure to use TwoWay bindings (in Update No. 3 above, they are standard OneWay):

 <ComboBox ItemsSource="{Binding OwnerTypes}" SelectedValuePath="Id" SelectedValue="{Binding Owner.OwnerTypeId,Mode=TwoWay}" SelectedItem="{Binding Owner.OwnerType,Mode=TwoWay}" DisplayMemberPath="Name" /> 

This setting updates the "OwnerTypeId" and "OwnerType" properties when I select an item in the combo box.

+1
source share

All Articles