0

I have a datagrid which holds the User information. Now once, I click on the selected row, I want to display the user information such as their roles and allow the user to edit the user roles by clicking on the combobox. Under my data template I have the combobox in my xaml. Since using the datatemplate, the combobox name couldnt be found, I am using the following method below to get the children from the grid.

Here is the code to get the children element:

private List<FrameworkElement> GetChildren(DependencyObject parent)
    {
        List<FrameworkElement> controls = new List<FrameworkElement>();

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); ++i)
        {
            var child = VisualTreeHelper.GetChild(parent, i);
            if (child is FrameworkElement)
            {
                controls.Add(child as FrameworkElement);
            }
            controls.AddRange(GetChildren(child));
        }

        return controls;
    }

I created the selection changed event for the datagrid:

 private void userDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var userRolesList = new User().getUserRoles();
        ComboBox cbUserRole = (ComboBox)GetChildren(userDataGrid).First(x => x.Name == "cbUserRole");
        cbUserRole.ItemsSource = userRolesList;


    }

Now when I run this code, I am being shown error message

Sequence contains no matching element

The same method I use for my textboxes, I am able to display the values and edit the values too. But for my combobox its not working the way it supposed to. Can someone please help me on this. Thanks.

This is my xaml code:

<DataGrid AutoGenerateColumns="False" Grid.Row="2" Grid.ColumnSpan="4" Grid.RowSpan="3" x:Name="userDataGrid" Margin="70,0.2,70,0" ItemsSource="{Binding}" SelectionChanged="userDataGrid_SelectionChanged">
        <DataGrid.Columns>
            <DataGridTextColumn Header="ID" Binding="{Binding UserId}"/>
            <DataGridTextColumn Header="Username" Binding="{Binding UserName}"/>
            <DataGridTextColumn Header="Email" Binding="{Binding UserEmail}"/>
            <DataGridTextColumn Header="User Role" Binding="{Binding UserRole}"/>
            <DataGridTextColumn Header="Created Date" Binding="{Binding UserCreatedDate}"/>
        </DataGrid.Columns>
        <DataGrid.RowDetailsTemplate>
            <DataTemplate>
                <Border BorderThickness="0" Background="BlanchedAlmond" Padding="10">
                    <StackPanel Orientation="Vertical">
                        <StackPanel Orientation="Horizontal">
                            <TextBlock FontSize="12" Text="User ID: " VerticalAlignment="Center" />
                            <TextBlock x:Name="txtBlockId" FontSize="16" Foreground="MidnightBlue" Text="{Binding UserId, Mode=TwoWay}" VerticalAlignment="Center" />
                        </StackPanel>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock FontSize="12" Text="First Name: " VerticalAlignment="Center" />
                            <TextBox x:Name="txtFirstName" FontSize="16" Foreground="MidnightBlue" Text="{Binding UserFirstName, Mode=TwoWay}" VerticalAlignment="Center" />
                        </StackPanel>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock FontSize="12" Text="Last Name: " VerticalAlignment="Center" />
                            <TextBox x:Name="txtLastName" FontSize="16" Foreground="MidnightBlue" Text="{Binding UserLastName}" VerticalAlignment="Center" />
                        </StackPanel>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock FontSize="12" Text="User Role: " VerticalAlignment="Center" />
                            <ComboBox x:Name="cbUserRole" FlowDirection="LeftToRight" FontSize="16" Foreground="MidnightBlue" HorizontalAlignment="Stretch" VerticalAlignment="Center" SelectionChanged="cbUserRole_Click"/>
                        </StackPanel>
                        <StackPanel>
                            <Button x:Name="btnUpdate" Content="Update" VerticalAlignment="Center" HorizontalAlignment="Right" Click="btnUpdate_Click"/>
                        </StackPanel>
                    </StackPanel>
                </Border>
            </DataTemplate>
        </DataGrid.RowDetailsTemplate>
    </DataGrid>

Thanks

Rai Vu
  • 1,595
  • 1
  • 20
  • 30
  • Possible duplicate of [Sequence contains no matching element](https://stackoverflow.com/questions/3994336/sequence-contains-no-matching-element) – mjwills Sep 05 '18 at 04:00
  • `GetChildren(userDataGrid)` does not return any elements with a `Name` of `"cbUserRole"`. Use the `Immediate Window` to view the results of `GetChildren(userDataGrid)` and see what it **is** returning. – mjwills Sep 05 '18 at 04:02
  • It would be easier if you use MVVM pattern, are you restricted to your current development strategy? – Mac Sep 05 '18 at 04:14
  • @mjwills its throwing `Exception thrown: 'System.NullReferenceException'`. But if in the selection change method i add a message box with dummy message, then the applciation able to find the combobox... – anonymous_apple Sep 05 '18 at 04:15
  • If I add the Messagebox in between it works.. this is weird but when i selecft items from the combobox its not selecting the item – anonymous_apple Sep 05 '18 at 04:26

2 Answers2

1

I've been seeing you asking around how to work with this, let me show you one way, hope this helps, but I recommend you read about MVVM patterns and frameworks like MVVMLight for WPF.

Well, for this, first you need to install Install-Package MvvmLight -Version 5.4.1

Then you may need to fix one reference issue, in the ViewModelLocator, remove all the usings and replace with:

using GalaSoft.MvvmLight.Ioc;
using CommonServiceLocator;

Now, your MainWindowView.xaml, it should like:

<Window x:Class="WpfApp2.MainWindow"
        x:Name="root"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm="clr-namespace:WpfApp2.ViewModel"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.DataContext>
        <vm:MainViewModel x:Name="Model"/>
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <DataGrid AutoGenerateColumns="False" x:Name="userDataGrid" Margin="70,0.2,70,0" ItemsSource="{Binding Users}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="ID" Binding="{Binding UserId}"/>
                <DataGridTextColumn Header="Username" Binding="{Binding UserName}"/>
                <DataGridTextColumn Header="Email" Binding="{Binding UserEmail}"/>
                <DataGridTextColumn Header="User Role" Binding="{Binding UserRole}"/>
                <DataGridTextColumn Header="Created Date" Binding="{Binding UserCreatedDate}"/>
            </DataGrid.Columns>
            <DataGrid.RowDetailsTemplate>
                <DataTemplate>
                    <Border BorderThickness="0" Background="BlanchedAlmond" Padding="10">
                        <StackPanel Orientation="Vertical">
                            <StackPanel Orientation="Horizontal">
                                <TextBlock FontSize="12" Text="User ID: " VerticalAlignment="Center" />
                                <TextBlock x:Name="txtBlockId" FontSize="16" Foreground="MidnightBlue" Text="{Binding UserId, Mode=TwoWay}" VerticalAlignment="Center" />
                            </StackPanel>
                            <StackPanel Orientation="Horizontal">
                                <TextBlock FontSize="12" Text="First Name: " VerticalAlignment="Center" />
                                <TextBox x:Name="txtFirstName" FontSize="16" Foreground="MidnightBlue" Text="{Binding UserFirstName, Mode=TwoWay}" VerticalAlignment="Center" />
                            </StackPanel>
                            <StackPanel Orientation="Horizontal">
                                <TextBlock FontSize="12" Text="Last Name: " VerticalAlignment="Center" />
                                <TextBox x:Name="txtLastName" FontSize="16" Foreground="MidnightBlue" Text="{Binding UserLastName}" VerticalAlignment="Center" />
                            </StackPanel>
                            <StackPanel Orientation="Horizontal">
                                <TextBlock FontSize="12" Text="User Role: " VerticalAlignment="Center" />
                                <ComboBox ItemsSource="{Binding Path=DataContext.UserRoles, ElementName=root}" SelectionChanged='CbUserRole_OnSelectionChanged' SelectedItem="{Binding UserRole}" x:Name="cbUserRole" FlowDirection="LeftToRight" FontSize="16" Foreground="MidnightBlue" HorizontalAlignment="Stretch" VerticalAlignment="Center" />
                            </StackPanel>
                            <StackPanel>
                                <Button x:Name="btnUpdate" Content="Update" VerticalAlignment="Center" HorizontalAlignment="Right" Command="{Binding UpdateCommand, ElementName=Model}" CommandParameter="{Binding}" />
                            </StackPanel>
                        </StackPanel>
                    </Border>
                </DataTemplate>
            </DataGrid.RowDetailsTemplate>
        </DataGrid>
    </Grid>
</Window>

Then in your code-behind, there's a little event handling that needs to be done, when changing the roles,

using System.Windows;
using System.Windows.Controls;
using WpfApp2.ViewModel;

namespace WpfApp2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public MainViewModel ViewModel => (MainViewModel) DataContext;

        private void CbUserRole_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            ComboBox cb = (ComboBox)sender;

            if (cb != null)
            {
                ViewModel.SelectedUserRole = (UserRole)cb.SelectedItem;
            }
        }
    }
}

Then you should create a ViewModel like so (ViewModel -> MainViewModel.cs):

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using GalaSoft.MvvmLight.Command;
using WpfApp2.Data;

namespace WpfApp2.ViewModel
{
    public class MainViewModel : INotifyPropertyChanged
    {
        public MainViewModel()
        {
            PopulateUserTestData();
            UpdateCommand = new RelayCommand<User>(UpdateUser);
        }

        private ObservableCollection<User> _users;

        public ObservableCollection<User> Users
        {
            get => _users;
            set
            {
                if (_users != value)
                {
                    _users = value;
                    NotifyPropertyChanged();
                }
            }
        }

        private UserRole _userRole;
        public UserRole SelectedUserRole
        {
            get => _userRole;
            set
            {
                if (_userRole != value)
                {
                    _userRole = value;
                    NotifyPropertyChanged();
                }
            }
        }

        public RelayCommand<User> UpdateCommand { get; }

        public IEnumerable<UserRole> UserRoles => Enum.GetValues(typeof(UserRole)).Cast<UserRole>();

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private void UpdateUser(User user)
        {
            Users.Single(u => u.UserId == user.UserId).UserRole = SelectedUserRole;

            // Do updates on your context (or in-memory).

            PrintUsersOnDebug();
        }

        #region Test data and diagnostics support

        private void PrintUsersOnDebug()
        {
            foreach (User user in Users)
            {
                Debug.WriteLine("Username: " + user.UserName + " Role: " + user.UserRole);
            }
        }

        private void PopulateUserTestData()
        {
            Users = new ObservableCollection<User>
            {
                new User
                {
                    UserId = 1,
                    UserCreatedDate = DateTime.Now,
                    UserEmail = "johndoe1@email.com",
                    UserFirstName = "John",
                    UserLastName = "Doe",
                    UserName = "johnd",
                    UserRole = UserRole.Administrator
                },
                new User
                {
                    UserId = 2,
                    UserCreatedDate = DateTime.Now,
                    UserEmail = "billgordon@email.com",
                    UserFirstName = "Bill",
                    UserLastName = "Gordon",
                    UserName = "billg",
                    UserRole = UserRole.SuperUser
                }
            };

            PrintUsersOnDebug();
        }

        #endregion
    }
}

Other related classes:

Data->User.cs

using System;

namespace WpfApp2.Data
{
    public class User
    {
        public int UserId { get; set; }
        public string UserName { get; set; }
        public string UserEmail { get; set; }
        public UserRole UserRole { get; set; }
        public DateTime UserCreatedDate { get; set; }
        public string UserFirstName { get; set; }
        public string UserLastName { get; set; }

    }
}

UserRole.cs

namespace WpfApp2
{
    public enum UserRole
    {
        Administrator,
        User,
        SuperUser
    }
}

Now since I just designed this test app to view the changing data in this case roles, I designed this to be just viewable on output window. As you change the roles and click the update button, inspect the output window.

enter image description here

Mac
  • 949
  • 8
  • 12
  • I am trying to use the Mvvm light but in my xaml file its showing MainViewModel does not exist in the namespace... – anonymous_apple Sep 05 '18 at 06:42
  • You have to make sure that the namespace is correct, in the example the namespace of the MainViewModel is WpfApp2.ViewModel. The file structure is that the MainViewModel.cs is inside a folder named ViewModel – Mac Sep 05 '18 at 06:51
  • Yes the namespace is correct. I am following exactly your code in a new project called WpfApp2 – anonymous_apple Sep 05 '18 at 06:56
  • Mac I was able to solve the namespace problem. I am just curious, If not I have many views and model how to implement it using the mvvmlight? – anonymous_apple Sep 05 '18 at 07:10
  • currently, once i install the MvvmLight using the Nuget packages it automatically created the ViewModel folder and inside it has two class files, MainViewModel.cs and ViewModelLocator.cs. As per your explanation above "Then you should create a ViewModel like so (ViewModel -> MainViewModel.cs):" .. Should I create a new folder view model and new Mainviewmodel cs file or use the same existing file? – anonymous_apple Sep 05 '18 at 07:12
  • Mac I am getting error in the MainViewModel.cs at ` public UserRole SelectedUserRole` . Its saying **MainViewModel.SelectedUserRole.get': not all code paths return a value** – anonymous_apple Sep 05 '18 at 07:20
  • @anonymous_apple, remove the MainViewModel created by mvvmlight for now use the ones that you have already. – Mac Sep 05 '18 at 07:29
  • Mac if you dont mind can you share me the source code. I need to cross check with my code – anonymous_apple Sep 05 '18 at 07:33
  • Still its showing me the same error **MainViewModel.SelectedUserRole.get': not all code paths return a value** – anonymous_apple Sep 05 '18 at 07:34
  • Ok, give me a moment. – Mac Sep 05 '18 at 07:34
  • If I want to create new Views or Model how can I create it using the mvvm light? – anonymous_apple Sep 05 '18 at 07:40
  • @anonymous_apple see the code here: https://github.com/raymartf31/magenicboard/tree/master/sample-wpf-project – Mac Sep 05 '18 at 07:49
  • @anonymous_apple, there's no difference in creating views, just keep in mind that it's not being recommended to have code in your code-behind with mvvm, your bindings, commands, logics should be on your viewmodel, that's just the general rule, there is more to that, please read. There are great tutorials online like this: https://www.pluralsight.com/courses/mvvm-light-toolkit-fundamentals – Mac Sep 05 '18 at 07:54
  • I am really confused on how to proceed with the MVVMLight. This looks very dificult... – anonymous_apple Sep 05 '18 at 08:26
  • MVVMLight is just a toolkit, as you can see in my code it's just the relaycommand that is from the MVVMLight, MVVM is the preferred pattern in developing apps right now in web, windows and mobile, so it is important that you understand how to do that, learn it step by step, this thread might give you more details: https://stackoverflow.com/questions/5421874/basic-concepts-of-mvvm-what-should-a-viewmodel-do – Mac Sep 05 '18 at 08:33
0

If you simply want to populate the ComboBox in the RowDetailsTemplate, you could handle its Loaded event:

private void cbUserRole_Loaded(object sender, RoutedEventArgs e)
{
    ComboBox cbUserRole = (ComboBox)sender;
    if (cbUserRole.ItemsSource == null)
        cbUserRole.ItemsSource = new User().getUserRoles();
}

XAML:

<StackPanel Orientation="Horizontal">
    <TextBlock FontSize="12" Text="User Role: " VerticalAlignment="Center" />
    <ComboBox x:Name="cbUserRole"
              Loaded="cbUserRole_Loaded"
              FlowDirection="LeftToRight" FontSize="16" Foreground="MidnightBlue" HorizontalAlignment="Stretch"
              VerticalAlignment="Center" 
              SelectionChanged="cbUserRole_Click"/>
</StackPanel>
mm8
  • 163,881
  • 10
  • 57
  • 88