【问题标题】:Binding of control visibility to ViewModel property doesn't work将控件可见性绑定到 ViewModel 属性不起作用
【发布时间】:2015-07-15 20:35:36
【问题描述】:

我正在将一个相当大的 WPF 应用程序从三层转换为 MVVM,并且正在学习 MVVM。到目前为止,我还没有深入研究 Bindings(等)的太多细节,所以请耐心等待。

我正在尝试将多个控件的 System.Windows.Visibility 绑定到 ViewModel 的公共属性(“状态”)。当父 TabItem 加载时,会根据需要读取和处理 State 属性。但是,当对属性进行后续更改时,它们似乎被忽略了。我已经(重新重新重新)检查了绑定,调试了转换器等,这让我发疯了。

视图模型:

public class MarketingListViewModel: IDisposable, INotifyPropertyChanged
{
    private UiState state;

    public event PropertyChangedEventHandler PropertyChanged;
    public UiState State 
    {
        get { return state; }
        set
        {
            if (state != value)
            {
                state = value;
                NotifyPropertyChanged("State");
            }
        }
    }

    public MarketingListViewModel() 
    {
        State = UiState.View;
    }

    private void NotifyPropertyChanged(String info) 
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

}

查看:

<UserControl 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:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
         mc:Ignorable="d" x:Class="WpfCrm.tabListManager" xmlns:DPH="clr-namespace:DPH" >

<UserControl.Resources>
    <DPH:MarketingListViewModel x:Key="listVM" />
    <!-- Note that the above line is giving me an "Object reference not set to an instance of an object" error -->
</UserControl.Resources>

<Grid x:Name="gridMain" DataContext="{StaticResource listVM}" >

    <Border Grid.Row="0" Grid.Column="1" Margin="10"
            Style="{StaticResource WidgetStyle}" >
        <Grid x:Name="gridListManagement" >
            <Label x:Name="labelManageLists" Content="Manage Lists" MouseDown="labelManageLists_MouseDown"
                Style="{StaticResource WidgetTitleStyle}"
                Grid.Row="0" Grid.Column="0" />

            <StackPanel Grid.Row="0" Grid.Column="2" Orientation="Horizontal"  HorizontalAlignment="Right" >
                <Label x:Name="llNewList" Content="new" MouseDown="llNewList_MouseDown"
                    Style="{StaticResource LinkLabelStyle}"
                    HorizontalAlignment="Right" />
                <Label x:Name="llCloseManageLists" Content="close" MouseDown="llCloseManageLists_MouseDown" 
                    Style="{StaticResource LinkLabelStyle}"
                    HorizontalAlignment="Right" />
            </StackPanel>

            <Label x:Name="labelListName" Content="Name" Grid.Row="1" Grid.Column="0" />
            <Grid Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" >
                <ComboBox x:Name="cbLists" SelectedIndex="-1" SelectionChanged="cbLists_SelectionChanged" IsReadOnly="True"
                          ItemsSource="{Binding Path=AllMarketingLists}" 
                          DisplayMemberPath="Name" 
                          SelectedValuePath="Id"
                          Visibility="{Binding Path=State, Converter={StaticResource ViewStateToVisibilityConverter} }"/>
                <TextBox x:Name="tbListName" 
                         Text="{Binding Path=OList.Name}"
                         Visibility="{Binding Path=State, Converter={StaticResource EditStateToVisibilityConverter} }"/>
            </Grid>

            <Label x:Name="labelListDescription" Content="Description" Grid.Row="2" Grid.Column="0" />
            <Grid Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" >
                <TextBlock x:Name="textblockListDescription" TextWrapping="Wrap" 
                           Text="{Binding Path=OList.Notes}"
                           Visibility="{Binding Path=State, Converter={StaticResource ViewStateToVisibilityConverter} }"
                           Grid.ColumnSpan="2" />
                <TextBox x:Name="tbListDescription" TextWrapping="Wrap"
                         Text="{Binding Path=OList.Notes}"
                         Visibility="{Binding Path=State, Converter={StaticResource EditStateToVisibilityConverter} }"
                         Grid.ColumnSpan="2" />
            </Grid>

            <StackPanel Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3" HorizontalAlignment="Right" Orientation="Horizontal" >
                <Button x:Name="buttonEditList" Content="Edit" Click="buttonEditList_Click"
                        Visibility="{Binding Path=State, Converter={StaticResource ViewStateToVisibilityConverter} }"
                        Width="60" Margin="3" />
                <Button x:Name="buttonSaveList" Content="Save" Click="buttonSaveList_Click"
                        Visibility="{Binding Path=State, Converter={StaticResource EditStateToVisibilityConverter} }"
                        Width="60" Margin="3" />
                <Button x:Name="buttonCancel" Content="Cancel" Click="buttonCancel_Click" 
                        Visibility="{Binding Path=State, Converter={StaticResource EditStateToVisibilityConverter} }"
                        Width="60" Margin="3" />
            </StackPanel>

        </Grid>
    </Border>

</Grid>

并且代码隐藏有一些方法,例如:

    private void buttonEditList_Click(object sender, RoutedEventArgs e) 
    {
        listVM.State = UiState.Edit;
    }

有没有人知道为什么控件在状态更改后没有更新其可见性?

非常感谢,

DPH

编辑——转换器:

[ValueConversion(typeof(WpfCrm.UiState), typeof(System.Windows.Visibility))]
public class EditStateToVisibilityConverter: IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        UiState state = (UiState)value;
        if (state == UiState.View) return Visibility.Collapsed;
        else return Visibility.Visible;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
}

[ValueConversion(typeof(WpfCrm.UiState), typeof(System.Windows.Visibility))]
public class ViewStateToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        UiState state = (UiState)value;
        if (state == UiState.View) return Visibility.Visible;
        else return Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
}

【问题讨论】:

  • 你的转换器代码是什么
  • 刚刚添加 - 谢谢!
  • 您是否在代码隐藏中的某处声明了 listVM?
  • 是 -- listVM = new MarketingListViewModel(); -- 在视图构造函数中的 InitializeComponent() 之后。
  • 非常感谢大家! SO 在十分钟内解决了 15 小时的撞头问题。

标签: c# wpf xaml mvvm


【解决方案1】:

您似乎使用了视图模型的两个不同实例,一个在 XAML 中声明和使用

<UserControl.Resources>
    <DPH:MarketingListViewModel x:Key="listVM" />
</UserControl.Resources>
<Grid DataContext="{StaticResource listVM}" >

还有一个在代码隐藏中(来自评论):

listVM = new MarketingListViewModel(); 

您当然应该只使用一个。因此,将声明后面的代码更改为

listVM = (MarketingListViewModel)Resources["listVM"]; 

【讨论】:

  • 我会将它放在构造函数中,而不是创建新 VM 的代码
  • @TonyVitabile 你的意思是什么?原始评论是“ listVM = new MarketingListViewModel(); -- 在视图的构造函数中 InitializeComponent() 之后”。我写了“更改声明后的代码”,所以它显然也在构造函数中。
【解决方案2】:

好的,有一个错误。您将 listVM 声明为

var listVM = new MarketingListViewModel();

但这不是 XAML 中的 listVM。在 XAML 中,您创建了 MarketingListViewModel 的另一个实例。因此,当您尝试更改在代码中声明的 listVM 时,没有任何反应,因为该对象不是您的 Grid 的 DataContext。

在您的 Click 处理程序中,您必须编写以下内容:

private void buttonEditList_Click(object sender, RoutedEventArgs e) 
{
    var _listVM = (MarketingListViewModel)FindResource("listVM");
    _listVM.State = UiState.Edit;
}

或者用这个替换代码隐藏中的 listVM 声明:

listVM = (MarketingListViewModel)FindResource("listVM");

那么您将不需要更改事件处理程序。

希望,它会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-12-14
    • 2012-04-03
    • 1970-01-01
    • 2010-10-04
    • 1970-01-01
    • 2021-04-20
    • 2015-08-23
    • 1970-01-01
    相关资源
    最近更新 更多