简体   繁体   中英

WPF MVVM Binding issues/questions

I'm doing my 1st C# WPF project with MVVM and I have few questions.

1st, I want to list some objects from my model, and when I click on one of them a form is displayed on the screen (or a created form by default). In my form, the binding is not working and I can't find why.

The 1st view-model who call the form view-model:

public class ZRoleViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public ZRoleViewModel()
    {
        IRepository<Role> mRole = new Repository<Role>();
        _roles = new ObservableCollection<Role>(mRole.GetAll());

        SelectedRole = new Role();
        SelectedIndex = -1;

        _editRole = new ZRoleEditViewModel();
    }

    private ObservableCollection<Role> _roles;
    private Role _selectedRole;
    private int _selectedIndex;
    private ZRoleEditViewModel _editRole;

    public ObservableCollection<Role> Roles
    {
        get => _roles;
        set
        {
            _roles = value;
            RaisePropertyChanged("Roles");
        }
    }

    public Role SelectedRole
    {
        get => _selectedRole;
        set
        {
            _selectedRole = value;
            RaisePropertyChanged("SelectedRole");
            if (_selectedRole != null && _editRole != null)
            {
                _editRole.Role = _selectedRole;
            }
        }
    }

    public int SelectedIndex
    {
        get => _selectedIndex;
        set
        {
            _selectedIndex = value;
            RaisePropertyChanged("SelectedIndex");
        }
    }

    public ZRoleEditViewModel EditRole { get => _editRole; set => _editRole = value; }
}

Its xaml:

<UserControl x:Class="WpfPlanAction.View.ZRoleUserControl"
         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:WpfPlanAction.View"
         xmlns:viewModel="clr-namespace:WpfPlanAction.ViewModel"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
    <viewModel:ZRoleViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
    <DataTemplate DataType="{x:Type viewModel:ZRoleEditViewModel}">
        <local:ZRoleEditUserControl/>
    </DataTemplate>
</UserControl.Resources>
<Grid>
    <DockPanel Margin="10">
        <StackPanel DockPanel.Dock="Left">
            <TextBlock Text="Rôles" FontWeight="Bold"/>
            <DataGrid ItemsSource="{Binding Roles}"
                  SelectedItem="{Binding SelectedRole}"
                  SelectedIndex="{Binding SelectedIndex}"
                  IsReadOnly="True"
                  SelectionMode="Single"
                  AutoGenerateColumns="False"
                  RowHeaderWidth="0">
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding Libelle}" Header="Libellé"></DataGridTextColumn>
                </DataGrid.Columns>
            </DataGrid>
        </StackPanel>
        <ContentControl Content="{Binding EditRole}"></ContentControl>
    </DockPanel>
</Grid>

Here, my DataGrid's binding is working well. My ContentControl displays my second UserControl, but when I'm selecting an object, the binding is not working in my form.

Here the second view-model:

    public class ZRoleEditViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public ZRoleEditViewModel()
    {
        _role = new Role();
    }

    private Role _role;

    public Role Role
    {
        get => _role;
        set
        {
            _role = value;
            RaisePropertyChanged("Role");
        }
    }
}

And the xaml:

<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:local="clr-namespace:WpfPlanAction.View"
         xmlns:viewModel="clr-namespace:WpfPlanAction.ViewModel"
         xmlns:Model="clr-namespace:WpfPlanAction.Model" x:Class="WpfPlanAction.View.ZRoleEditUserControl"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300" Loaded="UserControl_Loaded">
<UserControl.Resources>
    <CollectionViewSource x:Key="roleViewSource" Source="{Binding Role}" d:DesignSource="{d:DesignInstance {x:Type Model:Role}, CreateList=True}"/>
</UserControl.Resources>
<UserControl.DataContext>
    <viewModel:ZRoleEditViewModel/>
</UserControl.DataContext>
<Grid>
    <Grid x:Name="grid1" VerticalAlignment="Top" Margin="10,10,0,0" HorizontalAlignment="Left" DataContext="{StaticResource roleViewSource}" Width="280">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Label VerticalAlignment="Center" Grid.Row="0" Margin="3" HorizontalAlignment="Left" Grid.Column="0" Content="Abrege:"/>
        <TextBox x:Name="abregeTextBox" Width="209" VerticalAlignment="Center" Text="{Binding Abrege, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" Grid.Row="0" Margin="3,4,-85,4" Height="24" HorizontalAlignment="Left" Grid.Column="1"/>
        <Label VerticalAlignment="Center" Grid.Row="1" Margin="3" HorizontalAlignment="Left" Grid.Column="0" Content="Libelle:"/>
        <TextBox x:Name="libelleTextBox" Width="209" VerticalAlignment="Center" Text="{Binding Libelle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" Grid.Row="1" Margin="3,4,-84,4" Height="24" HorizontalAlignment="Left" Grid.Column="1"/>
    </Grid>
</Grid>

RaisePropertyChanged("Role") is called but the binding won't work.

I tried to do it with only one view-model and one user control, the binding works well, too well (but as expected), when i'm editing the form, the list is updated before I commit. maybe there is a better way to display the list and the editing form on the same page.

And in a 2nd time, I'm looking for a simple way to manage a relation many to many without using a specific view. By example: Role have a List parsonsByRole and Person have a List RolesByPerson, I thought to display a full list of Role and Add/Remove buttons in the Person editing form to populate RolesByPerson. Maybe there is a better way to do it.

Thanks!

Thanks to the comments I have now a better understanding how it's works.

Here my solution:

1st view-model:

public class ZRoleViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public ZRoleViewModel()
    {
        IRepository<Role> mRole = new Repository<Role>();
        _roles = new ObservableCollection<Role>(mRole.GetAll());

        SelectedIndex = -1;
        SelectedRole = new Role();

        _editRole = new ZRoleEditViewModel(SelectedRole);
        _editUC = new ZRoleEditUserControl(_editRole);
    }

    private ObservableCollection<Role> _roles;
    private Role _selectedRole;
    private int _selectedIndex;
    private ZRoleEditViewModel _editRole;
    private ZRoleEditUserControl _editUC;

    public ObservableCollection<Role> Roles
    {
        get => _roles;
        set
        {
            _roles = value;
            RaisePropertyChanged("Roles");
        }
    }

    public Role SelectedRole
    {
        get => _selectedRole;
        set
        {
            _selectedRole = value;
            RaisePropertyChanged("SelectedRole");
            if (_selectedRole != null && _editRole != null)
            {
                _editRole.Role = _selectedRole;
            }
        }
    }

    public int SelectedIndex
    {
        get => _selectedIndex;
        set
        {
            _selectedIndex = value;
            RaisePropertyChanged("SelectedIndex");
        }
    }

    public ZRoleEditUserControl EditUC { get => _editUC; }
}

And the view:

<UserControl x:Class="WpfPlanAction.View.ZRoleUserControl"
         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:WpfPlanAction.View"
         xmlns:viewModel="clr-namespace:WpfPlanAction.ViewModel"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
    <viewModel:ZRoleViewModel/>
</UserControl.DataContext>
<Grid>
    <DockPanel Margin="10">
        <StackPanel DockPanel.Dock="Left">
            <TextBlock Text="Rôles" FontWeight="Bold"/>
            <DataGrid ItemsSource="{Binding Roles}"
                  SelectedItem="{Binding SelectedRole}"
                  SelectedIndex="{Binding SelectedIndex}"
                  IsReadOnly="True"
                  SelectionMode="Single"
                  AutoGenerateColumns="False"
                  RowHeaderWidth="0">
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding Libelle}" Header="Libellé"></DataGridTextColumn>
                </DataGrid.Columns>
            </DataGrid>
        </StackPanel>
        <ContentControl Content="{Binding EditUC, Mode=OneTime}" />
    </DockPanel>
</Grid>

I load in the ContentControl the view instead of the view-model and I change the constructor of my edit view and view-model to be sure to not use the default constructor.

2nd view-model:

public class ZRoleEditViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public ZRoleEditViewModel(Role currentRole)
    {
        _role = currentRole;
    }

    private Role _role;

    public Role Role
    {
        get => _role;
        set
        {
            _role = value;
            RaisePropertyChanged("Role");
        }
    }
}

The view:

<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:local="clr-namespace:WpfPlanAction.View"
         xmlns:viewModel="clr-namespace:WpfPlanAction.ViewModel"
         xmlns:Model="clr-namespace:WpfPlanAction.Model" x:Class="WpfPlanAction.View.ZRoleEditUserControl"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300" Loaded="UserControl_Loaded">
<Grid>
    <Grid x:Name="grid1" VerticalAlignment="Top" Margin="10,10,0,0" HorizontalAlignment="Left" Width="280">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Label VerticalAlignment="Center" Grid.Row="0" Margin="3" HorizontalAlignment="Left" Grid.Column="0" Content="Abrege:"/>
        <TextBox x:Name="abregeTextBox" Width="209" VerticalAlignment="Center" Text="{Binding Role.Abrege, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" Grid.Row="0" Margin="3,4,-85,4" Height="24" HorizontalAlignment="Left" Grid.Column="1"/>
        <Label VerticalAlignment="Center" Grid.Row="1" Margin="3" HorizontalAlignment="Left" Grid.Column="0" Content="Libelle:"/>
        <TextBox x:Name="libelleTextBox" Width="209" VerticalAlignment="Center" Text="{Binding Role.Libelle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" Grid.Row="1" Margin="3,4,-84,4" Height="24" HorizontalAlignment="Left" Grid.Column="1"/>
    </Grid>
</Grid>

I remove the resources and the datacontext tags.

Its code behind:

    public partial class ZRoleEditUserControl : UserControl
{
    public ZRoleEditUserControl(ZRoleEditViewModel context)
    {
        InitializeComponent();
        this.DataContext = context;
    }
}

My main issue is fix, the binding is working well. Too well again, I guess I will have to clone my object Role when I'm creating the edit form or use UpdateSourceTrigger=Explicit with maybe a BindingGroup.

Thanks for your help!

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