Search code examples
wpfdata-bindingcomboboxtextbox

Bind object to combobox and textbox


I have the following model:

public class Tag : ObservableObject
{
    private int _id = -1;
    public int Id
    {
        get { return _id; }
        set
        {
            if (value != _id)
            {
                _id = value;
                OnPropertyChanged("Id");
            }
        }
    }

    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            if (value != _name)
            {
                _name = value;
                OnPropertyChanged("Name");
            }
        }
    }

    private string _freeText;
    public string FreeText
    {
        get { return _freeText; }
        set
        {
            if (value != _freeText)
            {
                _freeText = value;
                OnPropertyChanged("FreeText");
            }
        }
    }
}


public class Item : ObservableObject
{
    // ...

    private Tag _tag;
    public Tag Tag
    {
        get { return _tag; }
        set
        {
            if (value != _tag)
            {
                _tag= value;
                OnPropertyChanged("Tag");
            }
        }
    }
}

The following View Model:

public class AppViewModel : ViewModelBase
{
    private Item _item;
    public Item Item
    {
        get { return _item; }
        set
        {
            if (_item != value)
            {
                _item = value;
                OnPropertyChanged("Item");
            }
        }
    }

    private List<Tag> _tags;
    public List<Tag> Tags
    {
        get { return _tags; }
        set
        {
            if (_tags != value)
            {
                _tags = value;
                OnPropertyChanged("Tags");
            }
        }
    }

    //...
 }

Where my Tags list is populated with the following data:

Tags = new List<Tag>() 
{
   new Tag()
   {
     Id = 0,
     Name = "Free Text"
   },
   new Tag()
   {
     Id = 1,
     Name = "Tag 1"
   },
   new Tag()
   {
     Id = 2,
     Name = "Tag 2"
   },
   new Tag()
   {
     Id = 3,
     Name = "Tag 3"
   }
 }

And the following View:

<ComboBox x:Name="cmbTags"
          ItemsSource="{Binding Tags}"
          DisplayMemberPath="Name"
          SelectedItem="{Binding Item.Tag}"/>

<TextBox x:Name="txtTagFreeText" Text="{Binding ElementName=cmbTags, Path=SelectedItem.FreeText}">
    <TextBox.Style>
        <Style TargetType="TextBox">
            <Setter Property="Visibility" Value="Collapsed" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding ElementName=cmbTags, Path=SelectedItem.Id}" Value="0">
                    <Setter Property="Visibility" Value="Visible"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBox.Style>
</TextBox>

What I want to do is to have the ComboBox Items Source binded to my Tags list, and the SelectedItem binded to my Item. I managed to set that up.

Then, if the Tag with the Id 0 is selected, I want to show the TextBox, that is also set up.

What I am having trouble with, is to bind the Text of the TextBox, with the Property FreeText of my Item.Tag.

The binding is kinda working, but it's being applied to all my Items, so whenever I change my Item, the TextBox will allways show my last input.

I have also tried to change the TextBox Text binding to:

{Binding Item.Tag.FreeText}

But the I have the same problem.

Anyone knows what I am doing wrong? Do I need to post more code?

Any help or feedback is appreciated.

Thank you.


Solution

  • You are binding to a null instance the Item Property of the AppViewModel should Initialize with an instance, but this will not solve the problem you should bind the ComboBox with an other Property in the ViewModel lets name it SelectedTag in the setter of, handle the logic that you want.

    public class AppViewModel : ObservableObject
    {
        private Item _item = new Item();
        public Item Item
        {
            get { return _item; }
            set
            {
                if (_item != value)
                {
                    _item = value;
                    OnPropertyChanged("Item");
                }
            }
        }
    
        private Tag _SelectedTag;
    
        public Tag SelectedTag
        {
            get { return _SelectedTag; }
            set
            {
                if (_SelectedTag != value)
                {
                    _SelectedTag = value;
                    OnPropertyChanged("SelectedTag");
                    Item.Tag = _SelectedTag;
                    if (_SelectedTag.Id == 0)
                    {
                        _SelectedTag.FreeText = "";
                    }
                }
            }
        }
    
    
        private List<Tag> _tags;
        public List<Tag> Tags
        {
            get { return _tags; }
            set
            {
                if (_tags != value)
                {
                    _tags = value;
                    OnPropertyChanged("Tags");
                }
            }
        }
    
        // ...
    }
    

    and the XAML Code:

    <ComboBox x:Name="cmbTags"
          ItemsSource="{Binding Tags}"
          DisplayMemberPath="Name"
          SelectedItem="{Binding SelectedTag,Mode=TwoWay}"/> 
        <TextBox  Text="{Binding SelectedTag.FreeText,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
            <TextBox.Style>
                <Style TargetType="TextBox">
                    <Setter Property="Visibility"  Value="Collapsed"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding SelectedTag.Id}" Value="0">
                            <Setter Property="Visibility" Value="Visible"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </TextBox.Style>
    </TextBox>
    

    UPDATE

    An other way you could bind directly by listening on the Item PropertyChanged Event when the Tag changed Raise Tag Property on the AppViewModel and do you logic.

    public class AppViewModel : ObservableObject
    {
        private Item _item;
        public Item Item
        {
            get { return _item; }
            set
            {
                if (_item != value)
                {
                    _item = value;
                    _item.PropertyChanged -= _item_PropertyChanged;
                    _item.PropertyChanged += _item_PropertyChanged;
                    OnPropertyChanged("Item");
                }
            }
        }
    
        private List<Tag> _tags;
        public List<Tag> Tags
        {
            get { return _tags; }
            set
            {
                if (_tags != value)
                {
                    _tags = value;
                    OnPropertyChanged("Tags");
                }
            }
        }
    
        public AppViewModel()
        {
            Item = new Item();
            Tags = new List<Tag>()
            {
               new Tag()
               {
                 Id = 0,
                 Name = "Free Text"
               },
               new Tag()
               {
                 Id = 1,
                 Name = "Tag 1"
               },
               new Tag()
               {
                 Id = 2,
                 Name = "Tag 2"
               },
               new Tag()
               {
                 Id = 3,
                 Name = "Tag 3"
               }
             };
        }
    
        private void _item_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(Item.Tag))
            {
                if (Item.Tag.Id == 0)
                {
                    Item.Tag.FreeText = string.Empty;
                }
                OnPropertyChanged(nameof(Item));
            }
        }
    }
    

    And bind the ComboBox with The Item Tag directly

     <TextBox x:Name="txtTagFreeText" Text="{Binding Item.Tag.FreeText,Mode=TwoWay}">
            <TextBox.Style>
                <Style TargetType="TextBox">
                    <Setter Property="Visibility" Value="Collapsed" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Item.Tag.Id}" Value="0">
                            <Setter Property="Visibility" Value="Visible"/> 
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </TextBox.Style>
    </TextBox>