I tried to solve this problem for 1 week, But I couldn't! I was searching and reading many pages, including these:
(www.thomaslevesque.com) [WPF] HOW TO BIND TO DATA WHEN THE DATACONTEXT IS NOT INHERITED
(stackoverflow) How do I use WPF bindings with RelativeSource?
(stackoverflow) WPF - Binding error in DataGridComboboxColumn
(stackoverflow) WPF Error 40 BindingExpression path error: property not found on 'object'
What am I trying to do!?
I have a domain class and a table in SQL LocalDb by name of TermType (you can see its code below) with 5 properties:
My app reads TermType
table and It supposed to show them inside a DataGrid
like this:
But it do not show Start/End date for each term type because It failed in binding StartDate
/EndDate
properties in right way! and Also I get this error message at Output
window (not as exception):
System.Windows.Data Error: 40 : BindingExpression path error: 'StartDate' property not found on 'object' ''MonthName' (HashCode=38847270)'. BindingExpression:Path=StartDate; DataItem='MonthName' (HashCode=38847270); target element is 'ComboBox' (Name=''); target property is 'SelectedIndex' (type 'Int32')
Before I say more! Please check my UserControl
xaml file that related to this window:
<UserControl x:Class="PresentationWPF.View.UserPanels.UserControlTermTypeCrud"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PresentationWPF.View.UserPanels"
xmlns:userPanels="clr-namespace:PresentationWPF.ViewModel.Client.UserPanels"
xmlns:converters="clr-namespace:PresentationWPF.View.Converters"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Background="DarkSlateGray">
<UserControl.Resources>
<userPanels:TermTypeCrudViewModel x:Key="TermTypeCrudViewModel"/>
<converters:IndexToMonthConverter x:Key="IndexToMonthConverter"/>
</UserControl.Resources>
<Grid DataContext="{StaticResource TermTypeCrudViewModel}">
<Grid.RowDefinitions>
<RowDefinition Height="9*"/>
<RowDefinition/>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding TermTypes}" AutoGenerateColumns="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<!--
<DataGrid.Resources>
<userPanels:BindingProxy x:Key="MyProxy" Data="{Binding}"/>
</DataGrid.Resources>
-->
<DataGrid.Columns>
<DataGridTextColumn Header="Term Name" Binding="{Binding TypeName}" Width="120"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}" Width="*"/>
<DataGridTemplateColumn Header="Date Range" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="Start: "/>
<ComboBox Grid.Column="1" Grid.Row="0"
Style="{StaticResource ComboBoxMonthNamesStyle}"
SelectedIndex="{Binding StartDate,
Converter={StaticResource IndexToMonthConverter}}"
/>
<TextBlock Grid.Column="0" Grid.Row="1" Text="End: "/>
<ComboBox Grid.Column="1" Grid.Row="1"
Style="{StaticResource ComboBoxMonthNamesStyle}"
SelectedIndex="{Binding EndDate,
Converter={StaticResource IndexToMonthConverter}}"
/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
I know the problem at ComboBox
is in its second property binding SelectedIndex
that it try to find out 'StartDate
' property inside of MonthName
object, instead of TermTypes
from DataGrid ItemsSource="{Binding TermTypes}"
. But I do not know how to do it!? Even I tried to use RelativeSource
inside of SelectedIndex
property binding, But I may not use it correctly!!!
If in Visual Studio 2017 I hover mouse over StartDate
in ComboBox
, It show this message too:
Cannot resolve property 'StartDate' in data context of type 'PresentationWPF.View.UserPanels.TermTypeCrudViewModel'
I even try to use Freezable class
(as suggested in first link I put at begging) and as you can see I commented the line of DataGrid.Resources
, but even if I try to use it at ComboBox
, SelectedIndex
still Data
property of BindingProxy
object do not point to TermTypes
.
NOTE: I try to insert month names directly inside ComboBox
in xaml (by using ComboBoxItem
) and omit Style
binding like below code and It worked, But still I like to know how to fix the code while I am using Style
binding in that way:
<ComboBox Grid.Column="1" Grid.Row="0"
SelectedIndex="{Binding StartDate,
Converter={StaticResource IndexToMonthConverter}}"
>
<ComboBoxItem>January</ComboBoxItem>
<ComboBoxItem>February</ComboBoxItem>
<ComboBoxItem>March</ComboBoxItem>
<ComboBoxItem>April</ComboBoxItem>
<ComboBoxItem>May</ComboBoxItem>
<ComboBoxItem>June</ComboBoxItem>
<ComboBoxItem>July</ComboBoxItem>
<ComboBoxItem>August</ComboBoxItem>
<ComboBoxItem>September</ComboBoxItem>
<ComboBoxItem>October</ComboBoxItem>
<ComboBoxItem>November</ComboBoxItem>
<ComboBoxItem>December</ComboBoxItem>
</ComboBox>
I will appreciated to tell me how to solve this problem!?
Did I use MVVM and UnitOfWork in the right way!?
Any better suggestion to replace MonthName class or ComboBox Style in App.xaml!?
Or any other issues that may you see in my codes!? Thanks so much in Advanced.
In case you need to see/know about my other related classes and ... here they are:
TermType Class:
namespace Core.BusinessLayer.Domain
{
public partial class TermType
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public TermType()
{
Terms = new HashSet<Term>();
}
public int TermTypeId { get; set; }
public string TypeName { get; set; }
public string Description { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Term> Terms { get; set; }
}
}
(Model) TermTypeCrudService Class:
namespace PresentationWPF.Model
{
public class TermTypeCrudService : IDisposable
{
private readonly UnitOfWork _unitOfWork = new UnitOfWork(new AgsContext());
public bool IsDbDirty = false;
public IEnumerable<TermType> GetTermTypes() => _unitOfWork.TermTypes.GetAll();
public void Dispose()
{
if (IsDbDirty)
_unitOfWork.Complete();
_unitOfWork.Dispose();
}
}
}
(ViewModel) TermTypeCrudViewModel Class:
namespace PresentationWPF.ViewModel.Client.UserPanels
{
public class TermTypeCrudViewModel : INotifyPropertyChanged
{
private readonly TermTypeCrudService _termTypeCrudService = new TermTypeCrudService();
private ObservableCollection<TermType> _termTypes;
public ObservableCollection<TermType> TermTypes
{
get
{
return _termTypes;
}
set
{
_termTypes = value;
OnPropertyChanged();
}
}
public TermTypeCrudViewModel()
{
TermTypes = new ObservableCollection<TermType>(_termTypeCrudService.GetTermTypes());
}
public void Dispose() => _termTypeCrudService.Dispose();
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
(View - Code behind) class:
namespace PresentationWPF.View.UserPanels
{
public partial class UserControlTermTypeCrud : IUserPanelNavigation
{
private readonly TermTypeCrudViewModel _termTypeCrudViewModel;
public UserControlTermTypeCrud()
{
InitializeComponent();
_termTypeCrudViewModel = FindResource("TermTypeCrudViewModel") as TermTypeCrudViewModel;
}
public ObservableCollection<TermType> TermTypes
{
get => (ObservableCollection<TermType>)_termTypeCrudViewModel.TermTypes;
set => _termTypeCrudViewModel.TermTypes = value;
}
public event EventHandler OnNavigateEvent;
public string Title => "Term Types";
public UserControl NavigateToPanel { get; set; }
public void Dispose()
{
_termTypeCrudViewModel.Dispose();
}
}
}
App.xaml file:
<Application x:Class="PresentationWPF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PresentationWPF"
xmlns:userPanels="clr-namespace:PresentationWPF.ViewModel.Client.UserPanels"
Startup="Application_Startup">
<Application.Resources>
<!--
Create Month names list to use in ComboBox
-->
<userPanels:MonthName x:Key="MonthName" />
<Style TargetType="ComboBox" x:Key="ComboBoxMonthNamesStyle">
<Setter Property="DataContext" Value="{StaticResource MonthName}"/>
<Setter Property="ItemsSource" Value="{Binding MonthNamesCollection}"/>
<Setter Property="Width" Value="100"/>
</Style>
</Application.Resources>
</Application>
MonthName class:
namespace PresentationWPF.ViewModel.Client.UserPanels
{
public class MonthName
{
private ObservableCollection<string> _monthNamesCollection = new ObservableCollection<string>();
public ObservableCollection<string> MonthNamesCollection
{
get => _monthNamesCollection;
set => _monthNamesCollection = value;
}
public MonthName()
{
MonthNamesCollection.Add("January"); // 31 days
MonthNamesCollection.Add("February"); // 28 days in a common year and 29 days in leap years
MonthNamesCollection.Add("March"); // 31 days
MonthNamesCollection.Add("April"); // 30 days
MonthNamesCollection.Add("May"); // 31 days
MonthNamesCollection.Add("June"); // 30 days
MonthNamesCollection.Add("July"); // 31 days
MonthNamesCollection.Add("August"); // 31 days
MonthNamesCollection.Add("September"); // 30 days
MonthNamesCollection.Add("October"); // 31 days
MonthNamesCollection.Add("November"); // 30 days
MonthNamesCollection.Add("December"); // 31 days
}
}
}
Converter class that I used in ComboBox
SelectedIndex
:
namespace PresentationWPF.View.Converters
{
public class IndexToMonthConverter : IValueConverter
{
private DateTime _dateTime;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// Convert Month in 'value(DateTime)' ==> Index 0 to 11
if (value is DateTime b)
{
_dateTime = b;
return b.Month - 1;
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// Convert 'value(int)' 0 to 11 ==> Month number
if(value is int b)
return new DateTime(1,b + 1,1);
return _dateTime;
}
}
}
Do not set the DataContext property in a Combobox Style. Doing so breaks any DataContext-based Binding like {Binding StartDate}
.
<Style TargetType="ComboBox" x:Key="ComboBoxMonthNamesStyle">
<Setter Property="ItemsSource"
Value="{Binding MonthNamesCollection, Source={StaticResource MonthName}}"/>
...
</Style>