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: 19
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:
Shall I implement INotifyPropertyChange just because I target .NET Framework 4.8?
Thak you
 
You don't have to as long as you are not changing the data underneath while the DataContext is set.
 
You don't have to as long as you are not changing the data underneath while the DataContext is set.
Sorry. I am still not clear.
My list of structure names is produced at runtime by the code that trieas to match the names fetched from the Velocity database with the names fetched from an external database.
At this point, the code sends the automatically matched strings to the ListBox on the 5th column on my GUI.
The user can edit and accept/reject what is displayed in the ListBox but the code will not change the names anymore.
WHen the button CONFIRM is clicked the code must have access to the names that are marked as "Accepted".
I cannot quite understand what is identified as DataContex in the sequence of action described above.
Thank you
 
The purpose of INotifyPropertyChanged is to be able to tell the UI that it needs to redraw the screen when the data has changed. If you don't change the data after you've given the data to the UI to draw the first time, then there is really no need for you to implement the infrastructure to be able to tell the UI about changes.

In a two-way binding, the UI knows about the data changes it sends to your code, so again you don't really need to notify the UI (unless you reject the value it is trying to send to you and you store some other value instead).

In your post #11, you have your UpdateAutoStructures() method. You are setting DataContext to AutoNames.
 
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>
[CODE lang="csharp" title="Class declared ouside MainWindow inside WPFUI namespace"] public class EditableStructures
    {
        public bool IsAccepted { get; set; } = new bool();
        public string StrName { get; set; } = "";
    }

<Button Click="Accept_Click"
Grid.Row="1">Accept</Button>
</Grid>
</Window>
[/CODE]

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.
I got rid of the Memory-Access-Violation exception thrown when accessing a Velocity API method.
Now I am facing a new challenge in trying to display the editable structure names.
Following your example, I have declared the following class declared outside the MainWindow but inside the WPFUI namespace:

C#:
 public class EditableStructures
    {
        public bool IsAccepted { get; set; } = new bool();
        public string StrName { get; set; } = "";
    }
C#:

Inside MainWindow, I instantiate the above class and initialize it.

Instantiation of the class inside MainWindow:
 public static List<EditableStructures> AutoNames { get; set; } = new List<EditableStructures>();

When the user requests loading of the automatically remained strings,
the following code attempt to fill out the AutoNames list transferring the automatically generated names and initializing all CheckBoxes to False.


my method responding to the request to load the automatically renamed structure manes:
   private void AutoMatchedStruct_Click(object sender, RoutedEventArgs e)
        {
            if(structures.Count <= 0)
            {
                MessageBox.Show("Protocol structures must be loaded Before rensiming can start.", "Warning ", MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            PTAccess pt = new PTAccess();
            List<string> EditedList = new List<string>();
            EditedList = pt.StructuresAutomaticRename(structures);
            for (int i = 0; i < EditedList.Count; i++)
            {
                AutoNames[i].StrName = EditedList[i];
                AutoNames[i].IsAccepted = false;
            }
            UpdateAutoStrucNames();
        }

Unluckily I get an Out-Of-Range exception on the statement that transfers the first item from EditedList to AutoNames.StrName[0]. Please, see the attached screenshot.
It looks like memory has not been allocated for the AutoNames instance of the class.
How come?
Thank you so much.
 

Attachments

  • Out-Of-Range.png
    Out-Of-Range.png
    209.4 KB · Views: 4
Yes, that error is expected.

When you did this line:
C#:
public static List<EditableStructures> AutoNames { get; set; } = new List<EditableStructures>();
all it does is create an empty list. You didn't add any items into the list. Your for loop assumes that there are items in the list.

My recommendation is to change your for loop to add items to the list.
 
Yes, that error is expected.

When you did this line:
C#:
public static List<EditableStructures> AutoNames { get; set; } = new List<EditableStructures>();
all it does is create an empty list. You didn't add any items into the list. Your for loop assumes that there are items in the list.

My recommendation is to change your for loop to add items to the list.
It does not work because each AutoNames item is made up of a string and a bool.
I tried the following:

C#:
foreach(string st in EditedList)
{
    AutoNames.Add(st);
}

I cannot convert the string "st" to AutoNames datatype.
I also tried to add the pair (st, false) but it is not accepted because the method Add
expects only one argument.
I cannot get this right
 
I managed to fill out the AutoNames list as follows:

C#:
 foreach (string str in EditedList)
            {
                AutoNames.Add(new EditableStructures { StrName = str,  IsAccepted = false }) ;
                var DbgList = String.Join(",", AutoNames.Select(x => x));                       // DEBUG PURPOSE
                MessageBox.Show($"AutoNames:  {DbgList}");                        // DEBUG PURPOSE
            }

The messageBox does not show anything useful. It does not show the pair of values for each AutoNames item. However, hovering over AutoNames in the end of the loop, I see the pairs of values are there. Nevertheless, nothing appears in the ListBox.

I declared the DataContext as follows:


C#:
 public  MainWindow()
        {
            InitializeComponent();
            DataContext = this;
            PTAccess pt = new PTAccess();
            pt.InitializeVelocity();
        }

AutoNames is declared inside the class
public partial class MainWindow : Window
as follows:



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

However, AutoNames is filled out when the button "Load Auto-Matched-Structures" is clicked. AutoNames list is not filled out at initialization as in your example.
I am afraid the DataContext is not in the right place ......
Please, advise.
Thank you

Good news... All the edited strings now appear on the rightmost column and are indeed editable. I added one more statement with respect to your example:

C#:
EditableStructs.ItemSource = (System.Collections.IEnumerable)AutoNames;

Now I start working on getting back the edited values ... another pain ...
In fact, the following code:


C#:
var selectedPairs = AutoNames.Where(m => m.IsAccepted && !string.IsNullOrWhiteSpace(m.StrName));
            List<string> SavedStruct = selectedPairs.Select(m => m.StrName).ToList();  // EXTRACTS SELECTED STRUCTURES

Returns Null. I can see AutoNames contains 49 item pairs but the sifting statement returns an empty list.
"selectedPairs" is empty

Can you please help at your earliest convenience?
Thank you so much
 

Attachments

  • Editable-Structures-Displayed.png
    Editable-Structures-Displayed.png
    258.4 KB · Views: 7
Last edited:
It does not work because each AutoNames item is made up of a string and a bool.
I tried the following:

C#:
foreach(string st in EditedList)
{
    AutoNames.Add(st);
}

I cannot convert the string "st" to AutoNames datatype.
I also tried to add the pair (st, false) but it is not accepted because the method Add
expects only one argument.
I cannot get this right
C#:
foreach(string st in EditedList)
{
    var editableStructure = new EditableStructure
    {
       StrName = st,
       IsAccepted = false
    };
    AutoNames.Add(editableStructure);
}
 
C#:
foreach(string st in EditedList)
{
    var editableStructure = new EditableStructure
    {
       StrName = st,
       IsAccepted = false
    };
    AutoNames.Add(editableStructure);
}
Thanks. I managed to add the structure names as shown in message 71.
Unluckily I am not getting back the edited names from the ListBox to my code.
 
UPDATE:

I can now get back the names that have been marched as "accepted" through the Checkboxes. I just added the Mode = "TwoWaY" to the Binding of the ListBox.
Thank you for all your help
 
Yay!!! Congratulations!
 
Yay!!! Congratulations!
Sorry. Now I have another problem.
The editable structure names were displayed in the ListBox.
Let's say there were 10 names in all. So the ListBox index would go from 1 to 10.
Let's assume the user has edited and selected the names in positions 3,7,9.
So the only CheckBoxes that were checked are the ones whose ListBox index is 3,7,9.
My question is: How can the code get the indexes of the items that were checked? I cannot say "selected" items .
The statements you wrote for me are :

C#:
 var selectedPairs = AutoNames.Where(m => m.IsAccepted && !string.IsNullOrWhiteSpace(m.StrName));
            List<string> SavedStruct = selectedPairs.Select(m => m.StrName).ToList();

I need the exact position/index, concerning the ListBox order, of the checked items. That is because I have to generate copies of the checked structures. Velocity API needs the ID of the structure for which a copy has to be generated.
I have a list of structure IDs relative to the list of structure names that were displayed in the ListBox. So if the 3rd,7th, and 9th names were checked, I will use the 3rd, 7th, and 9th elements of the structure IDs list to generate the respective copies.
I understand the above-mentioned statements use a lambda-expression to extract the checked elements. I don't know how to get the indexes of the checked ListBox elements through that statement.
Thank you again

********* UPDATE *********
Sorry for my stupidity. It was an easy problem.
I was inspired by your code and wrote this simple test to make sure i could access the indexes of the checked items:

C#:
for (int k = 0; k < AutoNames.Count; k++)
            {
                if(AutoNames[k].IsAccepted && !string.IsNullOrWhiteSpace(AutoNames[k].StrName))
                {
                    TestIndexes.Add(k);
                }
            }
            DbgList = String.Join(",", TestIndexes.Select(x => x));                      // DEBUG PURPOSE
            MessageBox.Show($"Structure names:  {DbgList}");                        // DEBUG PURPOSE

The messageBox shows me exactly the right indexes.
 
Last edited:
I would like to ask you a question about the GUI you helped me develop.
I just released it to my colleagues and immediately they came up with requests.
They want a number of new features. The one that I am more worried about is numbering the items in the 4th and 5th rightmost columns. They are both ListBoxes.
I thought to concatenate each name with a progressive integer number (1,2,3,...) before
posting the structure names to the respective Listboxes. However, then I should remove the number from the checked (accepted) banes. This is not a brilliant solution. It is even risky because if the user deletes or alters the number beside a structure name then the resultant structures saved to Velocity database could be botched.
Here is the XAML code for the 4th and 5th columns:

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

        <ListBox x:Name="EditableStructs" Grid.Row="5" Grid.Column="30" Margin="20,20,20,40"  SelectionMode="Single"  Grid.ColumnSpan="4" Background="PowderBlue"
                            ItemsSource="{Binding AutoNames,Mode=TwoWay}" Height="600"  >
            <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>

Is there a better solution than concatenating the numbers to the names?

I am attaching a picture of the GUI.
The position of structure names in the 4th and 5th columns must be the same.


GUI.PNG
 
Add another field to the class that holds the screen index, bind (a one way) TextBlock to that field. When populating your list, set the appropriate value. Viola! You have the numbered list.
 
Back
Top Bottom