各种控件之间的双向数据绑定

时间:2013-05-15 15:22:02

标签: wpf

我对WPF很新,(我昨天开始),我对数据绑定非常困惑。我有一个Window模型,它包含一个名为Foo的小部件,它有自己的View Model。

小部件Foo将其Visibility TwoWay(通过BooleanToVisibilityConverter)绑定到其FooViewModel上的bool字段Visible。只要设置了Visible,FooViewModel就会实现INotifyPropertyChanged并触发PropertyChanged事件。

在Xaml for the Window中,只要单击一个按钮,它就会创建一个Foo。 Window的视图模型有另一个布尔字段,它将TwoWay绑定到其Foo View实例的Visibility。每当布尔字段被修改时,WIndow的视图模型实现INotifyPropertyChanged并触发PropertyChanged事件。

我期望这样做的是,只要窗口的布尔属性发生变化,就会设置Foo实例的可见性。当发生这种情况时,我希望更新Foo的View Model,因为Foo的可见性绑定是双向的。当Foo View Model更改其布尔字段时,我希望View更改其可见性。此外,我希望通知Window视图模型其Foo实例不再可见,因此Window的View模型将更新其自己的布尔字段。这是一个根本的误解吗?

我发布下面的(混淆)代码,如果它有助于揭示这种误解。感谢。

Window Xaml

<Window x:Class="XXX.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:XXX.ViewModel"
        xmlns:v="clr-namespace:XXX"
        Title="MainWindow" Height="350" Width="525" WindowStartupLocation="CenterOwner" WindowState="Maximized">
    <Window.Resources>
        <vm:AppViewModel x:Key="AppViewModel"/>
        <vm:TwoWayVisibilityConverter x:Key="BoolToVisibility" />
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource AppViewModel}}">
        <DockPanel>
            <Menu DockPanel.Dock="Top">
                <MenuItem Header="_Connection" Command="{Binding Authenticate}"/>
                <MenuItem Header="_About" Command="{Binding ShowAbout}"/>
                <MenuItem Header="_Logout" Command="{Binding Logout}"/>
                <MenuItem Header="_Configuration" Command="{Binding Configuration}"/>
                <MenuItem Header="_Info" Command="{Binding ShowInfo}"/>
            </Menu>
            <StackPanel>
            </StackPanel>
        </DockPanel>
        <Border HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Background="White"
                Padding="10"
                BorderThickness="0">
            <TextBlock Text="XXX"/>
        </Border>
        <Grid x:Name="Overlay" Panel.ZIndex="1000" DataContext="{Binding Source={StaticResource AppViewModel}}">
            <Border HorizontalAlignment="Stretch"
                VerticalAlignment="Stretch"
                Visibility="{Binding Path=Modal, Converter={StaticResource BoolToVisibility}, Mode=OneWay}"
                Background="DarkGray"
                Opacity=".7" />
                <v:Configuration HorizontalAlignment="Center"
                VerticalAlignment="Center" 
                Visibility="{Binding Path=ConfigurationVisible, Converter={StaticResource BoolToVisibility}, Mode=TwoWay}"/>
                <v:Connect HorizontalAlignment="Center"
                VerticalAlignment="Center" 
                Visibility="{Binding Path=AuthenticateVisible, Converter={StaticResource BoolToVisibility}, Mode=TwoWay}"/>
        </Grid>
    </Grid>

窗口视图模型

class AppViewModel : INotifyPropertyChanged
{
    [Import(typeof (IEventBus))] private IEventBus _bus;

    public AppViewModel()
    {
        Authenticate = new ForwardCommand(obj => ShowAuthenticationView(), obj => !AuthenticateVisible);
        Configuration = new ForwardCommand(obj => ShowConfigurationView(), obj => !ConfigurationVisible);
    }

    public bool Modal
    {
        get
        {
            return AuthenticateVisible || ConfigurationVisible;
        }
    }
    public ICommand Authenticate { get; set; }
    public bool AuthenticateVisible { get; set; }
    public ICommand ShowInfo { get; set; }
    public ICommand ShowAbout { get; set; }
    public ICommand Logout { get; set; }
    public ICommand Configuration { get; set; }
    public bool ConfigurationVisible { get; set; }

    private void ShowAuthenticationView()
    {
        AuthenticateVisible = !AuthenticateVisible;
        if (null != PropertyChanged)
        {
            PropertyChanged(this, new PropertyChangedEventArgs("AuthenticateVisible"));
            PropertyChanged(this, new PropertyChangedEventArgs("Modal"));
        }
    }

    private void ShowConfigurationView()
    {
        ConfigurationVisible = !ConfigurationVisible;
        if (null != PropertyChanged)
        {
            PropertyChanged(this, new PropertyChangedEventArgs("ConfigurationVisible"));
            PropertyChanged(this, new PropertyChangedEventArgs("Modal"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

UserControl Xaml

<UserControl x:Class="XXX.Connect"
             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:vm="clr-namespace:XXX.ViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <vm:ConnectViewModel x:Key="ViewModel"/>
        <vm:TwoWayVisibilityConverter x:Key="BoolToVisibility" />
    </UserControl.Resources>
    <Grid Width="280" 
            Height="173" 
            DataContext="{Binding Source={StaticResource ViewModel}}"
            Visibility="{Binding Path=Visible, Converter={StaticResource BoolToVisibility}, Mode=TwoWay}"
            Background="White">
        <Label Content="URL" Height="28" HorizontalAlignment="Left" Margin="12,12,0,0" Name="label1" VerticalAlignment="Top" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="102,12,0,0" Name="url" VerticalAlignment="Top" Width="169" Text="{Binding Path=Url, Mode=OneWayToSource}" TabIndex="0" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="102,70,0,0" Name="username" VerticalAlignment="Top" Width="171" Text="{Binding Path=Username, Mode=OneWayToSource}" TabIndex="2" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="102,41,0,0" Name="password" VerticalAlignment="Top" Width="169" Text="{Binding Path=Password, Mode=OneWayToSource}" TabIndex="1" />
        <Label Content="Username" Height="28" HorizontalAlignment="Left" Margin="12,39,0,0" Name="label3" VerticalAlignment="Top" />
        <Label Content="Password" Height="28" HorizontalAlignment="Left" Margin="12,68,0,0" Name="label2" VerticalAlignment="Top" />
        <DockPanel HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="13">
            <Button Content="OK" Height="23" HorizontalAlignment="Left"  Margin="5" Name="ok" VerticalAlignment="Top" Width="75" Command="{Binding ConnectCommand}" TabIndex="3" />
            <Button Content="Cancel" Height="23" HorizontalAlignment="Left" Margin="5" Name="cancel" VerticalAlignment="Top" Width="75" Command="{Binding CloseCommand}" TabIndex="4" />
        </DockPanel>
    </Grid>
</UserControl>

UserControl视图模型

internal class ConnectViewModel : INotifyPropertyChanged
{
    [Import(typeof (IEventBus))] private IEventBus _bus;

    public ConnectViewModel()
    {
        ConnectCommand = new ForwardCommand(obj => Fire(),
                                            obj =>
                                            Visible && !String.IsNullOrEmpty(Url) && !String.IsNullOrEmpty(Url) &&
                                            !String.IsNullOrEmpty(Url));
        CloseCommand = new ForwardCommand(obj => Hide(), obj => Visible);
    }

    public ICommand ConnectCommand { get; set; }

    public ICommand CloseCommand { get; set; }

    public string Url { get; set; }

    public string Username { get; set; }

    public string Password { get; set; }

    private bool _visible;

    public bool Visible
    {
        get { return _visible; }
        set { _visible = value; }
    }

    private void Fire()
    {
        _bus.Publish<SessionCreatedEvent, SessionEventHandler>(new SessionCreatedEvent(Url, Username, Password));
        Hide();
    }

    private void Hide()
    {
        Visible = false;
        if (null != PropertyChanged)
            PropertyChanged(this, new PropertyChangedEventArgs("Visible")); 
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

1 个答案:

答案 0 :(得分:0)

绑定始终适用于属性。因此,当您在Hide方法中提升Visible时,不会在实际属性中提升它。但属性是绑定引擎将设置的属性。如果将此绑定到另一个依赖项属性,则不会收到有关它的通知。 顺便说一句。什么是TwoWayVisibilityConverter? BooleanToVisibilityConverter完全能够处理双向绑定。

tl; dr使双向绑定正常工作(实际上甚至是单向绑定)你需要实现INotifyPropertyChanged 正确,这意味着,如果调用setter则提升属性。

public bool Visible
{
    get { return _visible; }
    set 
    { 
        _visible = value; 
        if (null != PropertyChanged)
            PropertyChanged(this, new PropertyChangedEventArgs("Visible")); 
    }
}