mjordan
mjordan

Reputation: 369

WPF Updating UI using Multiple Classes

I am teaching myself... I cannot understand why the UI won't update when a second class is involved. I am missing something basic and I don't get it.

In the first Class: I have two ObservableCollections bound to two WPF ListViews, which is bound correctly and works. I have a Command bound to a Button to move items from one Collection to the other, which works as expected.

In the second Class (backcode) I have implemented "Drag and Drop". On Drop I try to call the same Method (which is in the first Class and is used by the Button/Command. The Command is also in the first class).

On "Drag and Drop" the items are moved from one collection to the other (confirmed with Console.Writeline), however the UI doesn't update like it does with the Button/Command.

I believe the problem is that with "Drag and Drop" I am calling the Method from another class. I thought I could do that, but I must not be doing it right?

I have included everything from 4 files (xaml, backcode, class, relayCommand) so hopefully it is easy to reproduce. Can anyone tell me why & how to get this to work???

<Window x:Class="MultipleClassDragAndDrop.MainWindow"
    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:MultipleClassDragAndDrop"
    xmlns:ViewModel="clr-namespace:MultipleClassDragAndDrop.ViewModel"
    mc:Ignorable="d"
    Title="MainWindow" Height="716" Width="500">

<Window.Resources>
    <ViewModel:MultiColumnViewModel x:Key="MultiColumnViewModel"/>
</Window.Resources>

<Grid DataContext="{Binding Mode=OneWay, Source={StaticResource MultiColumnViewModel}}" >
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="20"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="20"/>
        <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>

    <Grid Grid.Row="0" Grid.Column="0" >
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="700"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>

        <StackPanel Grid.Row="0" Orientation="Vertical">
            <Button Content="Test Command" Command="{Binding Test_Command}"/>
        </StackPanel>


        <ListView  Grid.Row="1" Grid.Column="0" x:Name="ListView1" Background="Black" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
                  ItemsSource="{Binding ActiveJobListView1, UpdateSourceTrigger=PropertyChanged}" MouseMove="ListView1_MouseMove" >
            <ListView.ItemTemplate>
                <DataTemplate>
                    <GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold"  Width="150" Background="LightPink" BorderBrush="Transparent">

                        <StackPanel Orientation="Vertical" VerticalAlignment="Center" >
                            <TextBlock Text="{Binding JobID}" FontWeight="Bold" />
                            <TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
                        </StackPanel>
                    </GroupBox>

                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>

    <Grid Grid.Row="0" Grid.Column="2" >
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="700"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>


        <ListView  Grid.Row="1" Grid.Column="0" x:Name="ListView2" Background="Black" MinHeight="300" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
                   ItemsSource="{Binding ActiveJobListView2, UpdateSourceTrigger=PropertyChanged}" 
                   MouseMove="ListView1_MouseMove"
                   AllowDrop="True" Drop="ListView2_Drop" >
            <ListView.ItemTemplate>
                <DataTemplate>
                    <GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150"  Background="LightBlue" BorderBrush="Transparent">

                        <StackPanel Orientation="Vertical" VerticalAlignment="Center" >
                            <TextBlock Text="{Binding JobID}" FontWeight="Bold" />
                            <TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
                        </StackPanel>
                    </GroupBox>

                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Grid>

BackCode

using MultipleClassDragAndDrop.ViewModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;

namespace MultipleClassDragAndDrop
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();

    private void ListView1_MouseMove(object sender, MouseEventArgs e)
    {
        base.OnMouseMove(e);
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            int lb_itemIndex = ListView1.SelectedIndex;

            // Package the data.
            DataObject data = new DataObject();
            data.SetData("Int", lb_itemIndex);
            data.SetData("Object", this);

            // Inititate the drag-and-drop operation.
            DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
        }
    }


    private void ListView2_Drop(object sender, DragEventArgs e)
    {
        Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()}");

        base.OnDrop(e);

        int index = (int)e.Data.GetData("Int");

        // Call A Method In A Different Class
        objMultiColumnViewModel.AddAndRemove(index);

        e.Handled = true;
    }
}
}

My ViewModel Class

using MultipleClassDragAndDrop.ViewModel.Commands;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Input;

namespace MultipleClassDragAndDrop.ViewModel
{
public class ActiveJob : INotifyPropertyChanged
{
    #region INotifyPropertyChanged
    //INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
            Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
        }

    }
    #endregion

    public string _JobID;
    public string JobID
    {
        get { return _JobID; }
        set
        { _JobID = value; NotifyPropertyChanged("JobID"); }
    }

    public string _CustomerName;
    public string CustomerName
    {
        get { return _CustomerName; }
        set
        { _CustomerName = value; NotifyPropertyChanged("CustomerName"); }
    }
}

public partial class MultiColumnViewModel : INotifyPropertyChanged
{
    #region INotifyPropertyChanged
    //INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
            Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
        }

    }
    #endregion

    //Test Command
    private ICommand _Test_Command;
    public ICommand Test_Command
    {
        get
        {
            if (_Test_Command == null)
            {
                _Test_Command = new RelayCommand<object>(ExecuteTest_Command, CanExecuteTest_Command);
            }

            return _Test_Command;
        }
    }
    public bool CanExecuteTest_Command(object parameter)
    {
        return true;
    }
    public void ExecuteTest_Command(object parameter)
    {
        Mouse.OverrideCursor = Cursors.Wait;

        AddAndRemove(0);

        Mouse.OverrideCursor = Cursors.Arrow;
    }

    public void AddAndRemove(int selectedIndex)
    {
        Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()} Index = {selectedIndex}\n");

        ActiveJobListView2.Add(ActiveJobListView1[selectedIndex]);

        ActiveJobListView1.RemoveAt(selectedIndex);

        foreach (var item in ActiveJobListView1)
        {
            System.Console.WriteLine($"ActiveJobListView1: {item.JobID}, {item.CustomerName}");
        }

        System.Console.WriteLine($" ");

        foreach (var item in ActiveJobListView2)
        {
            System.Console.WriteLine($"ActiveJobListView2: {item.JobID}, {item.CustomerName}");
        }
    }

    public MultiColumnViewModel()
    {
        ActiveJobListView1 = new ObservableCollection<ActiveJob>();
        ActiveJobListView2 = new ObservableCollection<ActiveJob>();

        ActiveJobListView1.Add(new ActiveJob { JobID = "JOB100", CustomerName = "Smith" });
        ActiveJobListView1.Add(new ActiveJob { JobID = "JOB101", CustomerName = "Jones" });
        ActiveJobListView1.Add(new ActiveJob { JobID = "JOB102", CustomerName = "Black" });
    }

    #region Properties
    private ObservableCollection<ActiveJob> _ActiveJobListView1;
    public ObservableCollection<ActiveJob> ActiveJobListView1
    {
        get { return _ActiveJobListView1; }
        set
        {
            _ActiveJobListView1 = value;

            NotifyPropertyChanged("ActiveJobListView1");
        }
    }

    private ObservableCollection<ActiveJob> _ActiveJobListView2;
    public ObservableCollection<ActiveJob> ActiveJobListView2
    {
        get { return _ActiveJobListView2; }
        set
        {
            _ActiveJobListView2 = value;

            NotifyPropertyChanged("ActiveJobListView2");
        }
    }
    #endregion
}
}

Upvotes: 1

Views: 512

Answers (2)

Nawed Nabi Zada
Nawed Nabi Zada

Reputation: 2875

You have issues with different instances of the same class.

Change:

MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();

To:

var objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;

and it should work

EDIT:

What you are doing is strongly against MVVM principals.

EDIT-2

I had to do some modification to you code to make it work:

In your XAML:

<Window.DataContext>
    <local:MultiColumnViewModel/>
</Window.DataContext>

<Grid>
    <Grid.RowDefinitions>

In your MainWindow.cs:

    public MainWindow()
    {
        InitializeComponent();

        objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
    }

    private MultiColumnViewModel objMultiColumnViewModel;

enter image description here

Upvotes: 0

Christopher
Christopher

Reputation: 9804

When binding to a Collection, there are 3 kinds of ChangeNotification you need:

  1. The Notification that informs the UI if something was added or removed from the Collection. That is the only kind of Notification ObservableCollection provides.
  2. The Notification on the property exposing the ObservableCollection. Due to case 1 binding and the lack of a "add range", it is a bad idea to do bulk-modifications of a exposed List. Usually you create a new list and only then Expose it to the UI. In your case those would be the properties "ActiveJobListView1" and it's kind.
  3. The Notification on every property of every type exposed in the collection. That would be "ActiveJob" in your case.

Some of those are often forgotten, with Case 2 being the most common case. I wrote a small introduction into WPF and the MVVM pattern a few years back. maybe it can help you here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2/lets-talk-about-mvvm?forum=wpf

Upvotes: 1

Related Questions