Resolved Do I really need INotifyPropertyChanged?

mauede

Well-known member
Joined
Sep 1, 2021
Messages
103
Location
Northwood - UK
Programming Experience
Beginner
I suspect that INotifyPropertyChanged is overkill for what I would like to achieve.
I need:
  • to display a list of strings in a ListBox (the 5th column in the attached picture that is not working yet)
  • to allow the user to edit each ListBox Item and set the status of the contained string (Accepted/Rejected) through the associated CheckBox
  • when the button "Confirm" is clicked, to get access to the ListBox Item strings and the associated Checkbox to save to a database the strings that have "Accepted" status
The ListBox (5th column) is defined as follows:

C#:
<ListBox x:Name="AutoStrucNames" Grid.Row="5" Grid.Column="100" Height="600" Margin="80,20,0,40" Width="160"  SelectionMode="Single"  HorizontalAlignment="Left"  Grid.ColumnSpan="10" Background="PowderBlue"
                      DataContext="{Binding ElementName=main}" ItemsSource="{Binding AutoNames}" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                    <CheckBox x:Name="CheckAccepted" Margin="3" VerticalAlignment="Center" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
                    <TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay }"/>
                    </StackPanel>
                    </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

Questions:

  1. Do I need InotiftPropertyChanged to read the edited strings displayed by the ListBox when the Button "Confirm" is clicked?​
  2. Do I need to define the set of strings as a List<string> or as an "ObservableCollection<string>"?​
Thank you in advance
 

Attachments

  • My-GUI.PNG
    My-GUI.PNG
    347.7 KB · Views: 20
INotifyPropertyChanged is used when you update the underlying view model and want that change to reflect in the UI. It's a one way notification. If the UI is just used for display, and you won't be doing and dynamic changes you don't need it.

If the strings are not changing while the view as active, a List<string> is sufficient.
 
All I need is to display a list of names (strings) that can be edited by the user directly in the ListBox.
The code behind does not need to know about the editing.
The user will also check the CheckBox of the items whose content will be saved.
When the Button "Confirm" is clicked on then in the code that handles this button-click I have to get access to the ListBox Items (strings and CheckBox value) and save all the checked strings to a database.

This is how my ListBox is defined:

C#:
 <ListBox x:Name="AutoStrucNames" Grid.Row="5" Grid.Column="100" Height="600" Margin="80,20,0,40" Width="160"  SelectionMode="Single"  HorizontalAlignment="Left"  Grid.ColumnSpan="10" Background="PowderBlue"
                      DataContext="{Binding ElementName=main}" ItemsSource="{Binding AutoNames}" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <CheckBox x:Name="CheckAccepted" Margin="3" VerticalAlignment="Center" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
                        <TextBox Text="{Binding AutoNames, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay }"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>


I send the strings to be displayed and editable as follows:


C#:
AutoStrucNames.ItemsSource = (System.Collections.IEnumerable)AutoNames;

where AutoNames is defined as follows:

C#:
public List<string> AutoNames { get; set; } = new List<string>();


At runtime, I get an empty ListBox with a CheckBox visible for each item.
I am having problems with binding here but I do not know how to make the strings displayed.
Any help is more than welcome.
Thank you
 
You need to read up on how pathing works with WPF binding.

Once you have your binding setup correctly, you don't need to pull the strings from the list box, they will be updated in your view model.
 
I am struggling with getting the binding right.
I had no problem with binding a ListBox to a list of strings.
The present case is different because I have to bind a ListBox that is paved with TextBoxes.
I do not know whether I have to bind the ListBox or the TextBox, or both.

I did not follow an MVVM model. I only saw a video centred on an MVM model but the author used Calibur Micro that I meant to use but then I changed my mind as it is no more supported.
I felt uneasy at venturing into developing an MVVM model.
Therefore, I have to find out how to get the strings out of the ListBox, or the contained TextBoxes, that have the CheckBox checked.
Thank you.
 
If I have time later tonight, I'll try to come up with some sample code.
 
If I have time later tonight, I'll try to come up with some sample code.
Thank you.
I have got the book "WPF 4" Unleashed" by Adam Nathan.

Besides the difficulties with WPF I struggle with Velocity API (the library from Varian Medical Systems) whose documentation is scarce, misleading, and inaccurate. I am afraid it is not multi-threaded. That may be the cause of the exceptions I get ever now and then, but not regularly when I call the methods defined by Velocity API. A nightmare!
 
Besides the difficulties with WPF I struggle with Velocity API (the library from Varian Medical Systems) whose documentation is scarce, misleading, and inaccurate.
Try SharePoint documentation sometime... The best information is not from MS, but rather the cottage industry that has grown around it.
 
Give the following a try in a .NET Core WPF project:
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;

namespace SimpleWpfCore
{
    public class CrewMember
    {
        public bool IsDeployed { get; set; }
        public string Name { get; set; } = "";
    }

    public partial class MainWindow : Window
    {
        public List<CrewMember> Crew { get; } = new ()
            {
                new CrewMember { Name = "James T. Kirk" },
                new CrewMember { Name = "Spock" },
                new CrewMember { Name = "Leonard McCoy" },
                new CrewMember { Name = "Hikaru Sulu" },
                new CrewMember { Name = "Nyota Uhura" },
                new CrewMember { Name = "Montgomery Scott" },
                new CrewMember { Name = "Pavel Chekov" },
                new CrewMember { Name = "Red Shirt #1" },
                new CrewMember { Name = "Red Shirt #2" },
                new CrewMember { Name = "Red Shirt #3" },
            };

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
        }

        private void Accept_Click(object sender, RoutedEventArgs e)
        {
            var awayTeam = Crew.Where(m => m.IsDeployed && !string.IsNullOrWhiteSpace(m.Name));
            var names = String.Join(", ", awayTeam.Select(m => m.Name));
            MessageBox.Show($"{names} report to the Transporter Room.", "Away Team Members");
        }
    }
}

MainWindow.xaml:
<Window x:Class="SimpleWpfCore.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:SimpleWpfCore"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="400">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ListBox ItemsSource="{Binding Crew}"
                 Grid.Row="0">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>

                        <CheckBox IsChecked="{Binding IsDeployed, Mode=TwoWay}"
                                  Grid.Column="0"
                                  Padding="3,5"
                                  VerticalAlignment="Center" />
                        <TextBox Text="{Binding Name, Mode=TwoWay}"
                                 Grid.Column="1"
                                 HorizontalAlignment="Stretch" />
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Click="Accept_Click"
                Grid.Row="1">Accept</Button>
    </Grid>
</Window>

Notice that in the Accept_Click() I'm not trying to query the listbox. I'm simply looking at the Crew list that was bound to the ListBox on line 14 of the XAML.

The bindings on line 24 and 28 of the XAML bind to the IsDeployed and Name properties of each of the CrewMember instances. Since this is a two way binding, whenever the user updates the UI, the corresponding properties in the list get updated.

If you don't want to use .NET Core, and rather use .NET Framework, initializing the Crew list on lines 16-28 will have to happen before the line 33 where the DataContext is set. If you want to initialize (or update the list via code) after the DataContext is set, this is when you'll want to have the ObservableCollection and INotifyPropertyChanged to be able to tell the UI that you've updated things underneath it.
 
Try SharePoint documentation sometime... The best information is not from MS, but rather the cottage industry that has grown around it.
Thanks for the tip.
We just realized that Velocity API is not used by anyone in the UK and very likely by anyone in the US either. I posted an email to the large Medical Physicists (MP) community in the US where also MPs from other countries have memberships. I asked to be contacted by anyone using Velocity API. I was hit by a member from Australia who asked me if there exists any Velocity API documentation...
We are venturing on unchartered territory.
 
I adapted your example to my code. I must have made some mistake as before I was seeing the 5th column (editable ListBox) empty with the CheckBoxes. Now I don't see the 5th column anymore.
If you look at my GUI that I posted some days ago in this thread, the 4th column, from left to right, shows the list of structures fetched from the Velocity database. The 5th column (the editable ListBox) should show the automatic renaming of the strings in the 4th column according to the strings shown in the 1st column.
Basically, the task of the application is to semi-automatically rename the structure associated with a patient treatment plan according to the gold standard structure names that are stored in an external database (not Velocity).

I defined the class as you taught me to do:
C#:
 public class EditableStructures
        {
            public bool IsAccepted { get; set; }
            public string StrName {get; set;} = "";
        }

        public List<EditableStructures> AutoNames { get; set; } = new();

The structure names come from the Velocity database. I had already developed this part of the code in a Console UI application.
So I adapted your code to mine as follows:


C#:
     private void StructSets_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (StructSets.SelectedIndex == -1)
               return;
            string structSetValue = StructSets.SelectedItem.ToString();
            StrSetIndex = StructSets.SelectedIndex;
             emptyVelStructuresListbox();
             PTAccess pt = new PTAccess();
             strucNames = pt.GetStructures(StrSetIndex);
             if (strucNames == null)
             {
                   MessageBox.Show("Cannot find any structure for current structure-set. Abort script!.", "Error ", MessageBoxButton.OK, MessageBoxImage.Warning);
                    App.Current.Shutdown();                             // TERMINATE APPLICATION IF NO STRUCTURE-SET IS FOUND
              }
              UpdateVelStructures();  // DISPLAY STUCTURES FROM VELOCITY DATABASE 
                  
              List<string> EditedList = new List<string>();
              EditedList = pt.StructuresAutomaticRename(structures); // RENAME STRUCTURE AUTOMATICALLY
              for (int i = 0; i < EditedList.Count; i++)   
              {
                  AutoNames[i].IsAccepted = false;        // INITIALIZE ALL CHECKBOXES TO FALSE
                  AutoNames[i].StrName = EditedList[i];   // INITIALIZE EDITABLE LISTBOX
               }
              UpdateAutoStrucNames();
        }

public void UpdateAutoStrucNames()
        {
           DataContext = AutoNames;
        }


The XAML Definition of the editable ListBox is the same as yours but it was meant to be inserted in the right position


C#:
  <ListBox x:Name="VelStructures" Grid.Column="30" Grid.Row="5" Height="600"  d:ItemsSource="{d:SampleData}"  ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.CanContentScroll="True"
                 Margin="10,20,0,40" Width="160"  SelectionMode="Single"  HorizontalAlignment="Left"  Grid.ColumnSpan="10" Background="Yellow" />

       <ListBox  Grid.Row="5" Grid.Column="100" Height="600" Margin="80,20,0,40" Width="160"  SelectionMode="Single"  HorizontalAlignment="Left"  Grid.ColumnSpan="10" Background="PowderBlue"
                            ItemsSource="{Binding AutoNames}" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                   <Grid>
                         <Grid.ColumnDefinitions>
                             <ColumnDefinition Width="Auto"/>
                              <ColumnDefinition Width="*"/>
                           </Grid.ColumnDefinitions>
                           <CheckBox IsChecked="{Binding IsAccepted, Mode=TwoWay}" Grid.Column="0"  Padding="3,5" VerticalAlignment="Center"/>
                           <TextBox Text="{Binding  StrName, Mode=TwoWay}"  Grid.Column="1" HorizontalAlignment="Stretch"/>
                     </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

<Button x:Name="Confirm" Grid.Column="22"  Grid.Row="6"  Height="66" Content=" Commit" HorizontalAlignment="Left"  Background="Blue"
            Margin="10,10,5,0"  VerticalAlignment="Top" FontFamily="Arial Black" FontSize="20" Width="113"  Click="Accept_Click" />

The code for the COMMIT button is the same as yours but I have not tested it yet

C#:
 public void Accept_Click(object sender, RoutedEventArgs e)
        {
            var selectedPairs = AutoNames.Where(m => m.IsAccepted && !string.IsNullOrWhiteSpace(m.StrName));
            List<string> SavedStruct = selectedPairs.Select(m => m.StrName).ToList();  // EXTRACTS SELECTED STRUCTURES

            /*......... Place here the code to save the selected structures to Velocity database  ..............*/
          
            AppDomain.CurrentDomain.ProcessExit += (source, data) => { PTAccess.engine.logout(); }; // CLEAN UP VELOCITY ENVIRONMENT
            MessageBox.Show("The application will be closed.", "Cancel Application ", MessageBoxButton.OK, MessageBoxImage.Warning);
            App.Current.Shutdown();                             // TERMINATE APPLICATION
            Application.Current.MainWindow.Close();
        }

I am targeting .NET Framework 4.8
I would like to change it. I installed .NET Core 7.0

However, Visual Studio does not allow me to replace .NET Framework 4.8 with .NET Core 7.0
Shall I reinsert the code for INotifyPropertyChanged?
I thought I could get without it.
It is confusing.
If I reinstall the INotifyPropertyChanged I will not know how to change the bindings accordingly.
 
Stick with .NET 4.8. In my opinion it holds less surprises. I only did .NET 6 because I was trying to come up with an example with the least amount of boilerplate code, and this is achieved by all the C# language syntactic sugar added since the C# language that came with .NET Framework 4.8.
 
If you set the DataContext to the AutoNames list, then the binding {Binding AutoNames} will not work correctly because it will not find a property name AutoNames on the object List<EditableStructures>.

Notice that in my example, I setup the DataContext root to be this which was the MainWindow class. In my MainWindow class, I had a property named Crew which exposed the list of CrewMembers. The binding within the [DataTemplate[/icode] in my code then uses the CrewMember class as its root. This is why the bindings there just referred to IsDeployed and Name without having to do any pathing.
 
If you set the DataContext to the AutoNames list, then the binding {Binding AutoNames} will not work correctly because it will not find a property name AutoNames on the object List<EditableStructures>.

Notice that in my example, I setup the DataContext root to be this which was the MainWindow class. In my MainWindow class, I had a property named Crew which exposed the list of CrewMembers. The binding within the [DataTemplate[/icode] in my code then uses the CrewMember class as its root. This is why the bindings there just referred to IsDeployed and Name without having to do any pathing.
I don't get it. Sorry.
Isn't the following a property declared in MainWindow.xaml.cs
C#:
public class EditableStructures
        {
            public bool IsAccepted { get; set; }
            public string StrName {get; set;} = "";
        }

        public List<EditableStructures> AutoNames { get; set; } = new();
:
 
The bindings within the DataTemplate are relative to the items in the ItemsSource, the same way the bindings within the window are relative to the DataContext of the window.

I hope these help a little bit:
 
Back
Top Bottom