Cannot get selected ComboBox items in MainWindow.xaml.cs

I'd like to access ComboBox items (which are defined in another class) in MainWindow.xaml.cs, but I can't.

I'm new to C# and WPF. The purpose of this code is to get a selected ComboBox item as a search key. I have copied from many example codes on the Internet and now I'm completely lost. I don't even know which part is wrong. So, let me show the 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>

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 item = pointsComboBox SelectedItem as Point;
            //if(item != null)
            //{
            //    tb.Text = "You selected " + item.name + ".";
            //}
            MessageBox.Show("I'd like to show the item.name in the TextBox.");
        }
    }

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

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");
            }
        }
    }
}

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));
            }
        }
    }
}

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 = "High"},
                new Record {name = "Middle"},
                new Record {name = "Low"}
            };
        }

        internal void ExcelFileOpen(object sender, System.EventArgs e)
        {
            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 });
            MessageBox.Show("Assume that Excel data are loaded here.");
        }
    }
}

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 (This is a bug, but if you don't, the "Point Setting" items won't show up).

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

This code is supposed to show the selected item name in the TextBox.

If everything is in MainWindow.xaml.cs, the ComboBox items could be accessed. Since I split the codes into different classes, it has not been working. Please help me. Any suggestion would be helpful.

2 answers

  • answered 2019-08-24 01:14 AQuirky

    The problem is that the DataGridColumn is not part of the WPF logical tree and so your relative source binding will not work. The only way to get your binding to work is a type of kluge (very common with WPF in my experience). Create a dummy element that is in the logical tree and then reference that.

    So

    <FrameworkElement x:Name="dummyElement" Visibility="Collapsed"/>
    <DataGrid ItemsSource="{Binding channels}"
        SelectedItem="{Binding SelectedRow, Mode=TwoWay}"
              Margin="0,0,0,-0.2">
    

    Then your binding will look like this

    <ComboBox x:Name="piontsComboBox"
                                  ItemsSource="{Binding DataContext.points,
                            Source={x:Reference dummyElement}}"
                                  SelectionChanged="PrintText"
                                  DisplayMemberPath="name" 
                                  SelectedValuePath="name"
                                  Margin="5"
                                  SelectedItem="{Binding DataContext.SelectedPoint,
                            Source={x:Reference dummyElement},
                            Mode=TwoWay}"/>
    

  • answered 2019-08-24 03:05 IvanJazz

    Your binding does work. You can make use of the sender object to achieve what you wanted.

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