简体   繁体   中英

Strange behavior of MVVM binding to property

I am beginner in MVVM. I am writing simple app called Members. This is my member class (model):

class Member: INotifyPropertyChanged
{
    public Member(string name)
    {
        Name = name;
        _infoCommand = new InfoCommand(this);
    }

    string _name;

    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            _name= value;
            notify("Name");
            notify("CanShowInfo");
        }
    }

    public override string ToString()
    {
        return Name;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    void notify(string property_name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property_name));
        }
    }

    private ICommand _infoCommand;
    public ICommand InfoCommand
    {
        get
        {
            return _infoCommand;
        }
        set
        {
            _infoCommand = value;
        }
    }

    public bool CanShowInfo
    {
        get
        {
            return _infoCommand.CanExecute(null);
        }
    }
}

This is my InfoCommand class:

class InfoCommand : ICommand
{
    Member _member;

    public InfoCommand(Member member)
    {
        _member = member;
    }

    public bool CanExecute(object parameter)
    {
        if (_member.Jmeno.Length > 0)
            return true;
        else
            return false;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        MessageBox.Show("I am " + _member.Name);
    }
}

This is my MemberViewModel class:

class MembersViewModel : INotifyPropertyChanged
{
    ObservableCollection<Member> _members = new ObservableCollection<Member>();

    public MembersViewModel()
    {
        Members.Add(new Member("Member1"));
        Members.Add(new Member("Member2"));
        Members.Add(new Member("Member3"));
        Members.Add(new Member("Member4"));
        Members.Add(new Member("Member5"));
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void notify(string property_name)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(property_name));
    }

    Member _selectedMember;

    public Member SelectedMember
    {
        get
        {
            return _selectedMember;
        }

        set
        {
            _selectedMember= value;
            notify("SelectedMember");
        }
    }

    public ObservableCollection<Member> Members
    {
        get
        {
            return _members;
        }
        set
        {
            _members = value;
        }
    }

    AddCommand _addCommand;

    public AddCommand AddCommand
    {
        get
        {
            if (_addCommand == null)
                _addCommand = new AddCommand(this);
            return _addCommand;
        }
    }
}

This is my AddCommand:

class AddCommand : ICommand { MembersViewModel _vm;

    public AddCommand(MembersViewModel vm)
    {
        _vm = vm;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        _vm.Members.Add(new Member("New Member")); //<-------------------------
    }
}

And finally my View:

<Window x:Class="mvvm_gabriel.View.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ViewModels="clr-namespace:mvvm_gabriel.ViewModel"
    Title="MainWindow" Height="482" Width="525">
<Window.Resources>
</Window.Resources>
<Window.DataContext>
    <ViewModels:MembersViewModel />
</Window.DataContext>
<Grid>
    <ListView ItemsSource="{Binding Members}" 
              SelectedItem="{Binding SelectedMember, Mode=TwoWay}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="100" />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="{Binding Name}" />
                    <Button Grid.Column="1" Content="Info" Width="50" HorizontalAlignment="Left" Command="{Binding InfoCommand}" IsEnabled="{Binding Path=CanShowInfo, Mode=OneWay}" />

                </Grid>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
    <TextBox Text="{Binding SelectedMember.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
    <Button Content="Add" Command="{Binding AddCommand}" />
</Grid>

When I click some member in my ListView, his name is shown in TextBox. Now I can edit this name and property of my Member object is updated automatically. When I delete name of some member completely (string.Length == 0), Info button in my member template is disabled.

I can also add new members by clicking Add button. Member is added to my observable collection and automatically shown in ListView.

Everything works perfectly as far as here.

But now: look at line marked like this <---------------------- in my AddCommand.Execute method. When I add new member to my collection, I automatically give him name "New Member" and everything works fine. I can then adit my member's name and my button is disabled automatically as described above. But when I give empty string as the name for new member in constructor on marked line, enabling of my Info button quits working. I can give my new member any name and my Info button is still disabled.

Can anyone explain it and suggest some solution, please?

Your button in the mainwindow is binding the IsEnabled of the button to a property in the model, but the command binding will also cause the button to interrogate the CanExecute() of the command.

<Button Grid.Column="1" Content="Info" Width="50" HorizontalAlignment="Left" Command="{Binding InfoCommand}" IsEnabled="{Binding Path=CanShowInfo, Mode=OneWay}" />

This can lead to confusing behavior, as seen in your case.

You can basically remove the IsEnabled binding of the button, and add the property changed handler to the InfoCommand.

public class InfoCommand : ICommand
{
    Member _member;

    public InfoCommand(Member member)
    {
        _member = member;
        _member.PropertyChanged += _member_PropertyChanged;
    }

    private void _member_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Name")
            RaiseCanExecuteChanged();
    }

    private void RaiseCanExecuteChanged()
    {
        if (CanExecuteChanged != null)
            CanExecuteChanged(this, EventArgs.Empty);
    }

    public bool CanExecute(object parameter)
    {
        if (_member.Name.Length > 0)
            return true;
        else
            return false;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        MessageBox.Show("I am " + _member.Name);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM