简体   繁体   中英

Multiple Combo Boxes with shared Binding - Display error after first selection from box

I've been stumped on an issue I've run across in the last few days now and would like some help figuring it out.

I'm developing a WPF application that on its first run will prompt the user to manually assign detected serial ports to arbitrary 'channels', used throughout the application and later interface for displaying data etc.

One of the key features is once a port has been assigned in a combo box, it is then no longer available for selection in the others (using the .IsEnabled Property of the ComboBoxItem Class).

The issue I've run into is that while initially everything works fine - each combo box is set, opening the next sees the previous selection greyed out - if I attempt to go back to a combo box I've previously set it displays an empty drop down . It looks as if the drop-down is still active but that the window that contains the items hasn't been sized properly.

Screen Captures:

Items are successfully disabled in subsequent combo boxes

Returning to an already selected box results in a blank drop down (blue circle)

Here's my XAML code for the combo boxes:

<StackPanel Grid.Column="1" Name="ComboPanel" Margin="5, 20, 5, 5">
            <ComboBox Margin="0, 5, 0, 0" Width="100" Height="25" Name="cboxChannel0"  IsSynchronizedWithCurrentItem="false" ItemsSource="{Binding portCollectionItems, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="0" DropDownOpened="CboxChannel0_DropDownOpened" DropDownClosed="CboxChannel0_DropDownClosed" />
            <ComboBox Margin="0, 5, 0, 0" Width="100" Height="25" Name="cboxChannel1"  IsSynchronizedWithCurrentItem="false" ItemsSource="{Binding portCollectionItems, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="0" DropDownOpened="CboxChannel0_DropDownOpened" DropDownClosed="CboxChannel0_DropDownClosed" />
            <ComboBox Margin="0, 5, 0, 0" Width="100" Height="25" Name="cboxChannel2"  IsSynchronizedWithCurrentItem="false" ItemsSource="{Binding portCollectionItems, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="0" DropDownOpened="CboxChannel0_DropDownOpened" DropDownClosed="CboxChannel0_DropDownClosed" />
            <ComboBox Margin="0, 5, 0, 0" Width="100" Height="25" Name="cboxChannel3"  IsSynchronizedWithCurrentItem="false" ItemsSource="{Binding portCollectionItems, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="0" DropDownOpened="CboxChannel0_DropDownOpened" DropDownClosed="CboxChannel0_DropDownClosed" />
            <ComboBox Margin="0, 5, 0, 0" Width="100" Height="25" Name="cboxChannel4"  IsSynchronizedWithCurrentItem="false" ItemsSource="{Binding portCollectionItems, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="0" DropDownOpened="CboxChannel0_DropDownOpened" DropDownClosed="CboxChannel0_DropDownClosed" />
            <ComboBox Margin="0, 5, 0, 0" Width="100" Height="25" Name="cboxChannel5"  IsSynchronizedWithCurrentItem="false" ItemsSource="{Binding portCollectionItems, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="0" DropDownOpened="CboxChannel0_DropDownOpened" DropDownClosed="CboxChannel0_DropDownClosed" />
            <ComboBox Margin="0, 5, 0, 0" Width="100" Height="25" Name="cboxChannel6"  IsSynchronizedWithCurrentItem="false" ItemsSource="{Binding portCollectionItems, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="0" DropDownOpened="CboxChannel0_DropDownOpened" DropDownClosed="CboxChannel0_DropDownClosed" />
            <ComboBox Margin="0, 5, 0, 0" Width="100" Height="25" Name="cboxChannel7"  IsSynchronizedWithCurrentItem="false" ItemsSource="{Binding portCollectionItems, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="0" DropDownOpened="CboxChannel0_DropDownOpened" DropDownClosed="CboxChannel0_DropDownClosed" />
            <ComboBox Margin="0, 5, 0, 0" Width="100" Height="25" Name="cboxChannel8"  IsSynchronizedWithCurrentItem="false" ItemsSource="{Binding portCollectionItems, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="0" DropDownOpened="CboxChannel0_DropDownOpened" DropDownClosed="CboxChannel0_DropDownClosed" />
            <ComboBox Margin="0, 5, 0, 0" Width="100" Height="25" Name="cboxChannel9"  IsSynchronizedWithCurrentItem="false" ItemsSource="{Binding portCollectionItems, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="0" DropDownOpened="CboxChannel0_DropDownOpened" DropDownClosed="CboxChannel0_DropDownClosed" />
            <ComboBox Margin="0, 5, 0, 0" Width="100" Height="25" Name="cboxChannel10" IsSynchronizedWithCurrentItem="false" ItemsSource="{Binding portCollectionItems, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="0" DropDownOpened="CboxChannel0_DropDownOpened" DropDownClosed="CboxChannel0_DropDownClosed" />
            <ComboBox Margin="0, 5, 0, 0" Width="100" Height="25" Name="cboxChannel11" IsSynchronizedWithCurrentItem="false" ItemsSource="{Binding portCollectionItems, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="0" DropDownOpened="CboxChannel0_DropDownOpened" DropDownClosed="CboxChannel0_DropDownClosed" />
        </StackPanel>

And here's the behind code snippets relevant to the boxes:

public partial class PortWindow : Window, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public ObservableCollection<ComboBoxItem> portCollectionItems { get; set; }
    public ComboBoxItem selectedItem;
    public bool serialPortsSet { get; set; }

    public ComboBoxItem SelectedItem 
    {
        get { return selectedItem; }
        set 
        { 
            if (selectedItem == value) 
                return;

            selectedItem = value;
            OnPropertyChanged("IsEnabled");
        }
    }
    public PortWindow()
    {
        InitializeComponent();
        DataContext    = this;
        serialPortsSet = false;
        portCollectionItems = new ObservableCollection<ComboBoxItem>();

        for (int i = 0; i < ActiveSerialPorts.DetectedPorts.Count(); i++)
        {
            if (i == 0) 
            {
                portCollectionItems.Add(new ComboBoxItem { Content = "<-Select->" });
            }

            portCollectionItems.Add(new ComboBoxItem { Content = ActiveSerialPorts.DetectedPorts[i] }); // Populates collection with a list of serial port names from another class 
        }
    }

    void CboxChannel0_DropDownOpened(object sender, EventArgs e)
    {
        ComboBox comboBox     = sender as ComboBox;
        string selectedString = comboBox.SelectionBoxItem as string;
        selectedItem          = comboBox.SelectedItem as ComboBoxItem;

        foreach (ComboBoxItem portItems in portCollectionItems) 
        {
            if (portItems.Content == selectedItem.Content) 
            {
                portItems.IsEnabled = true; //re-enables the previously disabled selection in case the assigned port needs changing
            }
        }
    }

    void CboxChannel0_DropDownClosed(object sender, EventArgs e)
    {
        ComboBox comboBox = sender as ComboBox;
        selectedItem      = comboBox.SelectedItem as ComboBoxItem;
        string itemString = selectedItem.Content.ToString();

        if (!itemString.Contains("<-Select->"))
        {
            foreach (ComboBoxItem portItems in portCollectionItems) 
            {
                if (portItems.Content == selectedItem.Content) 
                {
                    portItems.IsEnabled = false; // disables the selected item in the observable collection 
                    return;
                }
            }  
        }
    }

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

I'm inclined to believe there's something small but vital I'm missing with regards to the binding. I originally thought it was the altering of a property within a shared collection that was causing the problem, but after supressing all of the code in the handlers and running it the issue persisted.

Any help would be appreciated!

You're sharing ComboBoxItems among different ComboBoxes. Those are visual elements and you're trying to share them among different parents. I'm not surprised it breaks.

You need each ComboBox to have a distinct collection of ComboBoxItem instances. You can most simply do that by exposing your items as ObservableCollection<String> and letting each ComboBox create its own ComboBoxItems. Bind the collection the same way you're binding the collection you've got; I think that declaring and populating that collection should almost be the only change you need to make, unless you need to disable port items for all boxes at once, rather than just the one they were selected in. Your SelectedItem on the combobox will be the string, though, rather than `ComboBoxItem, so the guts of those loops will have to change a bit.

When you disable a ComboBoxItem, it will be disabled only for the ComboBox it belongs to, naturally. If you want COM4 disabled for all boxes, you'll have to do that in a loop.

Or you could go a little more MVVM: If this were me, I would create the series of ComboBoxes in a templated ItemsControl bound to a collection of some class that had a SelectedPort property, and I would use a custom class for the items as well, just a simple thing with String PortName and bool IsPortEnabled . I'd bind IsPortEnabled to ComboBoxItem.IsEnabled in the XAML. There'd be a lot less code, but it's a big jump conceptually from where you're at now. We can go there if you're interested.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM