I have a Project in WPF C# .NET 8 that gets data from sql server and display a StudentName, a DataGrid that Contains places where that student is interested in called PlacesOfInterest. In the PlacesOfInterest datagrid, there are three Column ComboBoxes named Country, Province and District respectively.
The Country Column is getting a list of all the Countries from a Table Called Countries in the Database which Contains all the Countries in the World. The Province ComboBoxColumn is getting a list of all the Provinces in each Country and the District ComboBoxColumn is getting a list of Districts in each Province.
Here is the fully working repos in github.Now I tried to convert the PlacesOfInterest Datagrid to a re-usable control because it is recurring in my project. Then I came up with Something like this:
the Re-usable DataGrid named PlacesDataGrid.xaml:
<UserControl x:Name="userControl" x:Class="DataGridBindingExampleCore2.CustomControls.PlacesDataGrid"
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:conveters="clr-namespace:DataGridBindingExampleCore2.Converters"
xmlns:vm="clr-namespace:DataGridBindingExampleCore2.CustomControls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<vm:PlacesDataGridViewModel x:Key="viewModel"/>
<conveters:ConvProvinceID x:Key="ConvProvinceID" />
<conveters:ConvDistrictID x:Key="ConvDistrictID" />
</UserControl.Resources>
<Grid>
<DataGrid x:Name="DatagridPlaces"
AutoGenerateColumns="False"
ItemsSource="{Binding DataGridItemsSource, ElementName=userControl}"
SelectedItem="{Binding SelectedPlace}"
ColumnHeaderStyle="{StaticResource MaterialDesignFlatButton}" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="Country">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox DataContext="{Binding ElementName=userControl, Mode=OneWay}"
Width="120"
DisplayMemberPath="{Binding CountryPathvalue}"
ItemsSource="{Binding Countries, Source={StaticResource viewModel}}"
SelectedValue="{Binding CountrySelectedValue}"
SelectedValuePath="{Binding CountryPathvalue}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Province">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource ConvProvinceID}">
<Binding Path="{Binding ProvinceIDvalue}" />
<Binding Path="Provinces" Source="{StaticResource viewModel}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="ComboBox">
<ComboBox DataContext="{Binding ElementName=userControl, Mode=OneWay}"
Width="120"
DisplayMemberPath="{Binding ProvincePathValue}"
ItemsSource="{Binding CurrentProvinces, Source={StaticResource viewModel}}"
SelectedValue="{Binding ProvinceSelectedValue}"
SelectedValuePath="{Binding ProvincePathValue}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="District">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource ConvDistrictID}">
<Binding Path="{Binding DistrictIDvalue}" />
<Binding Path="Districts" Source="{StaticResource viewModel}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="ComboBox">
<ComboBox DataContext="{Binding ElementName=userControl, Mode=OneWay}"
Width="120"
DisplayMemberPath="{Binding DistrictPathValue}"
ItemsSource="{Binding CurrentDistricts, Source={StaticResource viewModel}}"
SelectedValue="{Binding DistrictSelectedValue}"
SelectedValuePath="{Binding DistrictPathValue}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
And Here is the Code-Behind of PlacesDatagrid.xaml:
using System.Collections;
using System.Windows;
using System.Windows.Controls;
namespace DataGridBindingExampleCore2.CustomControls
{
public partial class PlacesDataGrid : UserControl
{
public PlacesDataGrid()
{
InitializeComponent();
}
public static readonly DependencyProperty DataGridItemsSourceProperty =
DependencyProperty.Register("DataGridItemsSource", typeof(IEnumerable), typeof(PlacesDataGrid), new PropertyMetadata(null));
public IEnumerable DataGridItemsSource
{
get { return (IEnumerable)GetValue(DataGridItemsSourceProperty); }
set { SetValue(DataGridItemsSourceProperty, value); }
}
public static readonly DependencyProperty CountryPathvalueProperty =
DependencyProperty.Register("CountryPathvalue", typeof(string), typeof(PlacesDataGrid), new PropertyMetadata(null));
public string CountryPathvalue
{
get { return (string)GetValue(CountryPathvalueProperty); }
set { SetValue(CountryPathvalueProperty, value); }
}
public static readonly DependencyProperty CountrySelectedValueProperty =
DependencyProperty.Register("CountrySelectedValue", typeof(object), typeof(PlacesDataGrid), new PropertyMetadata(null));
public object CountrySelectedValue
{
get { return (object)GetValue(CountrySelectedValueProperty); }
set { SetValue(CountrySelectedValueProperty, value); }
}
public static readonly DependencyProperty ProvincePathValueProperty =
DependencyProperty.Register("ProvincePathValue", typeof(string), typeof(PlacesDataGrid), new PropertyMetadata(null));
public string ProvincePathValue
{
get { return (string)GetValue(ProvincePathValueProperty); }
set { SetValue(ProvincePathValueProperty, value); }
}
public static readonly DependencyProperty ProvinceSelectedValueProperty =
DependencyProperty.Register("ProvinceSelectedValue", typeof(object), typeof(PlacesDataGrid), new PropertyMetadata(null));
public object ProvinceSelectedValue
{
get { return (object)GetValue(ProvinceSelectedValueProperty); }
set { SetValue(ProvinceSelectedValueProperty, value); }
}
public static readonly DependencyProperty ProvinceIDvalueProperty =
DependencyProperty.Register("ProvinceIDvalue", typeof(string), typeof(PlacesDataGrid), new PropertyMetadata(null));
public string ProvinceIDvalue
{
get { return (string)GetValue(ProvinceIDvalueProperty); }
set { SetValue(ProvinceIDvalueProperty, value); }
}
public static readonly DependencyProperty DistrictPathValueProperty =
DependencyProperty.Register("DistrictPathValue", typeof(string), typeof(PlacesDataGrid), new PropertyMetadata(null));
public string DistrictPathValue
{
get { return (string)GetValue(DistrictPathValueProperty); }
set { SetValue(DistrictPathValueProperty, value); }
}
public static readonly DependencyProperty DistrictSelectedValueProperty =
DependencyProperty.Register("DistrictSelectedValue", typeof(object), typeof(PlacesDataGrid), new PropertyMetadata(null));
public object DistrictSelectedValue
{
get { return (object)GetValue(DistrictSelectedValueProperty); }
set { SetValue(DistrictSelectedValueProperty, value); }
}
public static readonly DependencyProperty DistrictIDvalueProperty =
DependencyProperty.Register("DistrictIDvalue", typeof(string), typeof(PlacesDataGrid), new PropertyMetadata(null));
public string DistrictIDvalue
{
get { return (string)GetValue(DistrictIDvalueProperty); }
set { SetValue(DistrictIDvalueProperty, value); }
}
}
}
And Here is the ViewModel named PlacesDataGridViewModel.cs where all the dropdowns of the ComboBoxes are stored and filter accordingly:
using CommunityToolkit.Mvvm.ComponentModel;
using DataGridBindingExampleCore2.Models;
using System.Collections.ObjectModel;
using System.Linq;
namespace DataGridBindingExampleCore2.CustomControls
{
public partial class PlacesDataGridViewModel : ObservableObject
{
public PlacesDataGridViewModel()
{
LoadDataAsync();
}
private async void LoadDataAsync()
{
this.Countries = new ObservableCollection<CountriesModel>(await DAL.LoadCountriesAsync());
this.Provinces = new ObservableCollection<ProvincesModel>(await DAL.LoadProvincesAsync());
this.Districts = new ObservableCollection<DistrictsModel>(await DAL.LoadDistrictsAsync());
}
[ObservableProperty]
ObservableCollection<CountriesModel> countries;
[ObservableProperty]
ObservableCollection<ProvincesModel> provinces;
[ObservableProperty]
ObservableCollection<DistrictsModel> districts;
public object CurrentProvinces
{ get => new ObservableCollection<ProvincesModel>(Provinces.Where((p) => p.CountryName == SelectedPlace.CountryName)); }
public object CurrentDistricts
{ get => new ObservableCollection<DistrictsModel>(Districts.Where((p) => p.ProvinceID == SelectedPlace.ProvinceID)); }
[ObservableProperty]
PlacesOfInterest selectedPlace;
}
}
Now the issue is that, How can I tell the CountrySelectedValue
, ProvinceSelectedValue
, DistrictPathValue
that the values “CountryName “ , “ProvinceID” and “DistrictID” respectively are in the PlacesDataGridVieModel and not in where the Re-Usable Control is used, like here below in MainWindow.
<customControl:PlacesDataGrid
DataGridItemsSource="{Binding SelectedStudent.PlacesOfInterest}"
CountryPathvalue="CountryName"
CountrySelectedValue="{Binding CountryName, UpdateSourceTrigger=PropertyChanged}"
ProvincePathValue="ProvinceName"
ProvinceSelectedValue="{Binding ProvinceID, UpdateSourceTrigger=PropertyChanged}"
ProvinceIDvalue="ProvinceID"
DistrictPathValue="DistrictName"
DistrictSelectedValue="{Binding DistrictID, UpdateSourceTrigger=PropertyChanged}"
DistrictIDvalue="DistrictID"
/>
Here is the repos of the 2nd Version with the Re-Usable UserControl I tried.
And Here is the Database Script of the Database I was Using for Test.
Any help will be highly appreciated!