Search code examples
c#wpfsortingcomboboxdatagrid

ComboBox items don't show up until the 1st column is sorted


The 2nd column items "Point Setting" don't show up until the 1st column items are sorted, clicking on the header of the 1st column. The goal of this code is to link the 1st and 2nd column items, then use the 2nd column items as the search keys.

I'm new to C# and WPF. I tired to put sequential numbers in front of the 1st column items (1., 2., and so on) because I thought it would solve the problem if those items are initially sorted. But, no luck. I heard that ObservableCollection<> doesn't manage the input order, so once I changed it with List<>. But it didn't solve this problem, too.

Actually, I don't want to sort the 1st column; they should be fixed and no need to change the order/number at all.

To avoid any confusions, let me show my entire codes (sorry).

MainWindow.xaml:

<Window x:Class="XY.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:XY"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="454.4">
    <Grid>
        <DataGrid ItemsSource="{Binding channels}"
            SelectedItem="{Binding SelectedRow, Mode=TwoWay}"
                  Margin="0,0,0,-0.2">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding name}"
                                    Header="Channel" Width="Auto"/>
                <DataGridTemplateColumn Width="100" Header="Point Setting">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox x:Name="piontsComboBox"
                                      ItemsSource="{Binding DataContext.points,
                                RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                                      SelectionChanged="PrintText"
                                      DisplayMemberPath="name" 
                                      SelectedValuePath="name"
                                      Margin="5"
                                      SelectedItem="{Binding DataContext.SelectedPoint,
                                RelativeSource={RelativeSource AncestorType={x:Type Window}},
                                Mode=TwoWay}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
        <TextBox x:Name="tb" Width="140" Height="30" Margin="10,250,200,30"></TextBox>
        <Button x:Name="Browse_Button" Content="Browse" Margin="169,255,129.6,0"
                    Width="75" Click="Browse_Button_Click" Height="30" VerticalAlignment="Top"/>
    </Grid>
</Window>

MainWindow.xaml.cs:

using System;
using System.Windows;
using System.Windows.Controls;

namespace XY
{
    public partial class MainWindow : Window
    {
        public GridModel gridModel { get; set; }
        public MainWindow()
        {
            InitializeComponent();
            gridModel = new GridModel();
            this.DataContext = gridModel;
        }

        private void Browse_Button_Click(object sender, RoutedEventArgs e)
        {
            WakeupClass clsWakeup = new WakeupClass();
            clsWakeup.BrowseFile += new EventHandler(gridModel.ExcelFileOpen);
            clsWakeup.Start();
        }

        void PrintText(object sender, SelectionChangedEventArgs args)
        {
            var comboBox = sender as ComboBox;
            var selectedPoint = comboBox.SelectedItem as Point;
            tb.Text = selectedPoint.name;
        }
    }

    public class WakeupClass
    {
        public event EventHandler BrowseFile;
        public void Start()
        {
            BrowseFile(this, EventArgs.Empty);
        }
    }
}

ViewModelBase.cs:

using System.ComponentModel;

namespace XY
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
    }
}

Point.cs:

namespace XY
{
    public class Point : ViewModelBase
    {
        private string _name;
        public string name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged("name");
            }
        }

        private int _code;
        public int code
        {
            get { return _code; }
            set
            {
                _code = value;
                OnPropertyChanged("code");
            }
        }
    }
}

Record.cs:

namespace XY
{
    public class Record : ViewModelBase
    {
        private string _name;
        public string name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged("name");
            }
        }

        private int _PointCode;
        public int PointCode
        {
            get { return _PointCode; }
            set
            {
                _PointCode = value;
                OnPropertyChanged("PointCode");
            }
        }

        private Record _selectedRow;
        public Record selectedRow
        {
            get { return _selectedRow; }
            set
            {
                _selectedRow = value;
                OnPropertyChanged("SelectedRow");
            }
        }

        private Point _selectedPoint;
        public Point SelectedPoint
        {
            get { return _selectedPoint; }
            set
            {
                _selectedPoint = value;
                _selectedRow.PointCode = _selectedPoint.code;
                OnPropertyChanged("SelectedRow");
            }
        }
    }
}

GridModel.cs:

using System.Collections.ObjectModel;
using System.Windows;

namespace XY
{
    public class GridModel : ViewModelBase
    {
        public ObservableCollection<Record> channels { get; set; }
        public ObservableCollection<Point> points { get; set; }
        public GridModel()
        {
            channels = new ObservableCollection<Record>() {
                new Record {name = "1. High"},
                new Record {name = "2. Middle"},
                new Record {name = "3. Low"}
            };
        }

        internal void ExcelFileOpen(object sender, System.EventArgs e)
        {
            points = new ObservableCollection<Point> { new Point { } };

            MessageBox.Show("Please assume that Excel data are loaded here.");
            points.Add(new Point { name = "point1", code = 1 });
            points.Add(new Point { name = "point2", code = 2 });
            points.Add(new Point { name = "point3", code = 3 });
            points.Add(new Point { name = "point4", code = 4 });
        }
    }
}

The procedure goes like:

  1. Click on the "Browse" button to load the data.

  2. Click on the 1st column "Channel" to sort the list (GOAL: I'd like to GET RID OF this step).

  3. Click on the "Point Setting" ComboBox to select the items (point1, point2, ..., etc.).

... I don't know if ObservableCollection<> is appropriate here. If List<> or any other type is better, please change it. Any suggestion would be helpful. Thank you in advance.


Solution

  • Change your points ObservableCollection like such, because you're setting the reference of the collection after the UI is rendered, you would need to trigger the PropertyChanged event to update the UI.

    private ObservableCollection<Point> _points;
    public ObservableCollection<Point> points
    {
        get { return _points; }
        set
        {
            _points = value;
            OnPropertyChanged(nameof(points));
        }     
    }
    

    An alternative would be to first initialise your collection.

    public ObservableCollection<Point> points { get; set; } = new ObservableCollection<Point>();
    
    internal void ExcelFileOpen(object sender, System.EventArgs e)
    {
        // Do not re-initialise the collection anymore.
        //points = new ObservableCollection<Point> { new Point { } };
        points.Add(new Point { name = "point1", code = 1 });
        points.Add(new Point { name = "point2", code = 2 });
        points.Add(new Point { name = "point3", code = 3 });
    }