Второе свойство привязки ComboBox, указывающее на его первый объект привязки свойства

Я пытался решить эту проблему в течение 1 недели, но не смог! Я искал и читал много страниц, включая эти:

(www.thomaslevesque.com) [WPF] КАК ПРИВЯЗАТЬСЯ К ДАННЫМ, ЕСЛИ КОНТЕКСТ ДАННЫХ НЕ НАСЛЕДУЕТСЯ

(stackoverflow) Как использовать привязки WPF с RelativeSource?

(stackoverflow) WPF - ошибка привязки в DataGridComboboxColumn

(stackoverflow) Ошибка WPF 40 Ошибка пути BindingExpression: свойство не найдено в 'объекте'

Что я пытаюсь сделать!?

У меня есть доменный класс и таблица в SQL LocalDb по имени TermType (вы можете увидеть его код ниже) с 5 свойствами:

  • TermTypeId
  • Имя Типа
  • Описание
  • Дата начала
  • Дата окончания

Мое приложение читает таблицу TermType и должно отображать их внутри DataGrid следующим образом:

введите здесь описание изображения

Но он не показывает дату начала/окончания для каждого типа термина, потому что ему не удалось правильно связать свойства StartDate/EndDate! а также я получаю это сообщение об ошибке в окне Output (не как исключение):

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')

Прежде чем я скажу больше! Пожалуйста, проверьте мой файл UserControl xaml, относящийся к этому окну:

<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>

Я знаю, что проблема в ComboBox заключается во второй привязке свойства SelectedIndex, которая пытается найти свойство StartDate внутри объекта MonthName, а не TermTypes из DataGrid ItemsSource="{Binding TermTypes}". Но я не знаю, как это сделать!? Даже я пытался использовать RelativeSourceвнутри привязки свойства SelectedIndex, но я могу использовать его неправильно!!!

Если в Visual Studio 2017 я наведу указатель мыши на StartDate в ComboBox, также появится это сообщение:

Не удается разрешить свойство StartDate в контексте данных типа PresentationWPF.View.UserPanels.TermTypeCrudViewModel.

Я даже пытаюсь использовать Freezable class (как было предложено в первой ссылке, которую я поставил при попрошайничестве), и, как вы можете видеть, я прокомментировал строку DataGrid.Resources, но даже если я попытаюсь использовать ее в ComboBox, SelectedIndex по-прежнему Data свойство объекта BindingProxy не указывает до TermTypes.

ПРИМЕЧАНИЕ. Я пытаюсь вставить названия месяцев непосредственно внутри ComboBox в xaml (с помощью ComboBoxItem) и опустить Stylebinding, как показано ниже, и это сработало, но все же мне нравится знаю, как исправить код, пока я использую Stylebinding таким образом:

<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>

Буду признателен, если подскажете, как решить эту проблему!?

Правильно ли я использовал MVVM и UnitOfWork!?

Есть ли лучшее предложение по замене класса MonthName или стиля ComboBox в App.xaml!?

Или любые другие проблемы, которые вы можете увидеть в моих кодах!? Большое спасибо в Advanced.

Если вам нужно увидеть/узнать о других моих связанных классах и... вот они:

Класс TermType:

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

(Модель) Класс TermTypeCrudService:

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:

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

Класс (Просмотр — Код позади):

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:

<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:

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

Класс преобразователя, который я использовал в 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;
        }
    }
}

person MRK    schedule 11.08.2018    source источник
comment
Binding StarDate это опечатка. Должно быть Binding StartDate.   -  person Clemens    schedule 11.08.2018
comment
@Clemens Оппссс! Спасибо! Я предполагаю, что это происходит во многих моих попытках и неудачных случаях! Теперь это работает, если я опускаю свойство Style и использую ComboBoxItem. Но мне нравится знать, как я могу это исправить, если я все еще хочу использовать стиль таким образом.   -  person MRK    schedule 11.08.2018
comment
{Binding StartDate, ...} ожидает свойство StartDate в текущем DataContext. Ваш стиль ComboBox устанавливает DataContext в экземпляр MonthName, у которого нет этого свойства. Решение: не устанавливайте DataContext в стиле.   -  person Clemens    schedule 11.08.2018


Ответы (1)


Не устанавливайте свойство DataContext в стиле Combobox. Это нарушает любую привязку на основе DataContext, например {Binding StartDate}.

<Style TargetType="ComboBox" x:Key="ComboBoxMonthNamesStyle">
    <Setter Property="ItemsSource"
            Value="{Binding MonthNamesCollection, Source={StaticResource MonthName}}"/>
    ...
</Style>
person Clemens    schedule 11.08.2018
comment
Большое спасибо! Оно живое!!!!! :D (Я имею в виду, работает;)) Последние 7 или 9 дней я делал все в любом месте в своих кодах, кроме App.xaml !!!! Не могли бы вы сказать мне, что вы думаете о еще 2 вопросах, которые я задал!? В частности, мой следующий план будет заключаться в следующем: когда пользователь выбирает дату начала в мае, в поле Дата окончания ComboBox должен показывать с июня по декабрь. - person MRK; 11.08.2018
comment
Обратите внимание: если StartDate и EndDate — это только месяцы, использование DateTime? в качестве типа свойства кажется странным. Просто используйте строку или, возможно, перечисление. Затем вы можете напрямую привязать свойство SelectedItem ComboBox без преобразователя. - person Clemens; 11.08.2018