Вызов IValueConverter для INotifyPropertyChanged работает только при запуске

Я пытаюсь изменить вид сетки в моем PDF-файле, основываясь на значении перечисления. Значение самого перечисления основано на меню, и при выборе параметра меню значение перечисления изменяется и вызывается PropertyChangedEventHandler.

Код класса, который включает PropertyChangedEventHandler, выглядит следующим образом:

public class ScreenToShow : INotifyPropertyChanged
{
    public enum MenuState { MenuPage, MenuChoice1, MenuChoice2, MenuChoice3};

    MenuState state;

    public MenuState _State
    {
        get { return state; }
        set
        {
            state = value;
            this.NotifyPropertyChanged("_State");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string propName)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    }
}

Чтобы изменить содержимое моего окна WPF, я связал видимость 4 сеток (каждая для одной опции меню + стартовая страница) с этим конвертером следующим образом: Ресурсы:

<Window.Resources>
    <local:ScreenToShow x:Key="myScreenToShow"></local:ScreenToShow>
    <local:VisibilityScreenConverter x:Key="myVisibilityScreenConverter"></local:VisibilityScreenConverter>
</Window.Resources>

XAML-код 1 из четырех сеток:

<Grid DataContext="{Binding Source={StaticResource myScreenToShow}}" Name="MenuPage" Grid.Row="1" Visibility="{Binding Path= _State, Converter={StaticResource myVisibilityScreenConverter}, ConverterParameter='menuPage', UpdateSourceTrigger=Default, Mode=TwoWay}">

И код конвертера выглядит следующим образом:

  class VisibilityScreenConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is ScreenToShow.State)
            {
                if((string)parameter == "menuPage")
                {
                    switch(value)
                    {
                        case ScreenToShow.State.MenuPage:
                            return Visibility.Visible;
                        default:
                            return Visibility.Hidden;
                    }
                }
                else if ((string)parameter == "menuChoice1")
                {
                    switch (value)
                    {
                        case ScreenToShow.State.MenuChoice1:
                            return Visibility.Visible;
                        default:
                            return Visibility.Hidden;
                    }
                }
                else if ((string)parameter == "menuChoice2")
                {
                    switch (value)
                    {
                        case ScreenToShow.State.MenuChoice2:
                            return Visibility.Visible;
                        default:
                            return Visibility.Hidden;
                    }
                }
                else // Menu choice 3
                {
                    switch (value)
                    {
                        case ScreenToShow.State.MenuChoice3:
                            return Visibility.Visible;
                        default:
                            return Visibility.Hidden;
                    }
                }
            }
            else
            {
                return Visibility.Visible;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

В MainWindow.cs используется следующий код:

    ScreenToShow screenToShowEnum = new ScreenToShow();

    public MainWindow()
    {
        DataContext = this;
        InitializeComponent();
        screenToShowEnum._State =  ScreenToShow.State.MenuPage;
    }

    private void MenuChoice1_Click(object sender, RoutedEventArgs e)
    {
        screenToShowEnum._State = ScreenToShow.State.MenuChoice1;
    }

    private void MenuChoice2_Click(object sender, RoutedEventArgs e)
    {
        screenToShowEnum._State = ScreenToShow.State.MenuChoice2;
    }

private void MenuChoice3_Click(object sender, RoutedEventArgs e)
        {
            screenToShowEnum._State = ScreenToShow.State.MenuChoice3;
        }

При запуске код работает нормально, и одновременно вызываются и NotifyPropertyChanged, и конвертер (проверил его при помощи console.writeline). Кроме того, видимость различных сеток обрабатывается как ожидалось. При выборе одного из пунктов меню также вызывается PropertyChangedHandler, как и ожидалось. Однако видимость различных сеток не меняется. Что я делаю не так, что видимость меняется на основе моего кода при запуске, но не при изменении значения моего перечисления на более поздней стадии?

Спасибо!

1 ответ

Решение

Проблема в том, что вы имеете в виду два совершенно разных ViewModel объекты, один из которых используется вашим представлением xaml, а другой - кодом, стоящим за файлом.

Либо установить DataContext свойство в коде файла или установите его в файле xaml:

<Window.DataContext>
    <StaticResource ResourceKey="myScreenToShow" />
</Window.DataContext>

если вы используете второй подход, убедитесь, что наложили DataContext в обработчиках событий:

private void MenuChoice1_Click(object sender, RoutedEventArgs e)
{
    (DataContext as ScreenToShow)._State = ScreenToShow.MenuState.MenuChoice1;
}

private void MenuChoice2_Click(object sender, RoutedEventArgs e)
{
    (DataContext as ScreenToShow)._State = ScreenToShow.MenuState.MenuChoice2;
}

private void MenuChoice3_Click(object sender, RoutedEventArgs e)
{
    (DataContext as ScreenToShow)._State = ScreenToShow.MenuState.MenuChoice3;
}

Демо-код:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplicationTest"
        xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero2" x:Class="WpfApplicationTest.MainWindow"
        mc:Ignorable="d"
        x:Name="win"
        WindowStartupLocation="CenterOwner"
        Title="MainWindow" Height="300" Width="300">
    <Window.Resources>
        <ResourceDictionary>
            <local:ScreenToShow x:Key="myScreenToShow"></local:ScreenToShow>
            <local:VisibilityScreenConverter x:Key="myVisibilityScreenConverter"></local:VisibilityScreenConverter>
        </ResourceDictionary>
    </Window.Resources>
    <Window.DataContext>
        <StaticResource ResourceKey="myScreenToShow" />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <Button Margin="5" Content="Menu 1" Click="MenuChoice1_Click" />
            <Button Margin="5" Content="Menu 2" Click="MenuChoice2_Click" />
            <Button Margin="5" Content="Menu 3" Click="MenuChoice3_Click" />
        </StackPanel>

        <Grid DataContext="{Binding Source={StaticResource myScreenToShow}}"
              Name="MenuPage1"
              Grid.Row="1"
              Visibility="{Binding Path= _State, Converter={StaticResource myVisibilityScreenConverter}, ConverterParameter='menuChoice1', UpdateSourceTrigger=Default, Mode=TwoWay}">
            <TextBlock Text="Menu 1" FontSize="24" />
        </Grid>
        <Grid DataContext="{Binding Source={StaticResource myScreenToShow}}"
              Name="MenuPage2"
              Grid.Row="1"
              Visibility="{Binding Path= _State, Converter={StaticResource myVisibilityScreenConverter}, ConverterParameter='menuChoice2', UpdateSourceTrigger=Default, Mode=TwoWay}">
            <TextBlock Text="Menu 2" FontSize="24" />
        </Grid>
        <Grid DataContext="{Binding Source={StaticResource myScreenToShow}}"
              Name="MenuPage3"
              Grid.Row="1"
              Visibility="{Binding Path= _State, Converter={StaticResource myVisibilityScreenConverter}, ConverterParameter='menuChoice3', UpdateSourceTrigger=Default, Mode=TwoWay}">
            <TextBlock Text="Menu 3" FontSize="24" />
        </Grid>
    </Grid>
</Window>

Код за файлами:

class VisibilityScreenConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is ScreenToShow.MenuState)
        {
            var state = (ScreenToShow.MenuState)value;
            if ((string)parameter == "menuPage")
            {
                switch (state)
                {
                    case ScreenToShow.MenuState.MenuPage:
                        return Visibility.Visible;
                    default:
                        return Visibility.Hidden;
                }
            }
            else if ((string)parameter == "menuChoice1")
            {
                switch (state)
                {
                    case ScreenToShow.MenuState.MenuChoice1:
                        return Visibility.Visible;
                    default:
                        return Visibility.Hidden;
                }
            }
            else if ((string)parameter == "menuChoice2")
            {
                switch (state)
                {
                    case ScreenToShow.MenuState.MenuChoice2:
                        return Visibility.Visible;
                    default:
                        return Visibility.Hidden;
                }
            }
            else // Menu choice 3
            {
                switch (state)
                {
                    case ScreenToShow.MenuState.MenuChoice3:
                        return Visibility.Visible;
                    default:
                        return Visibility.Hidden;
                }
            }
        }
        else
        {
            return Visibility.Visible;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

public class ScreenToShow : INotifyPropertyChanged
{
    public enum MenuState { MenuPage, MenuChoice1, MenuChoice2, MenuChoice3 };

    MenuState state;

    public MenuState _State
    {
        get { return state; }
        set
        {
            state = value;
            this.NotifyPropertyChanged("_State");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string propName)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    }
}

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void MenuChoice1_Click(object sender, RoutedEventArgs e)
    {
        (DataContext as ScreenToShow)._State = ScreenToShow.MenuState.MenuChoice1;
    }

    private void MenuChoice2_Click(object sender, RoutedEventArgs e)
    {
        (DataContext as ScreenToShow)._State = ScreenToShow.MenuState.MenuChoice2;
    }

    private void MenuChoice3_Click(object sender, RoutedEventArgs e)
    {
        (DataContext as ScreenToShow)._State = ScreenToShow.MenuState.MenuChoice3;
    }
}
Другие вопросы по тегам