Reputation: 55
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
Upvotes: 0
Views: 305
Reputation: 169220
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>
Upvotes: 0
Reputation: 959
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 = "[email protected]",
UserFirstName = "John",
UserLastName = "Doe",
UserName = "johnd",
UserRole = UserRole.Administrator
},
new User
{
UserId = 2,
UserCreatedDate = DateTime.Now,
UserEmail = "[email protected]",
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.
Upvotes: 1