Search code examples
wpfxamlitemscontrol

ItemsControl inside Popup duplicates the first item added


I have an ItemsControl inside a Popup with an ObservableCollection, which I'll refer to as simply 'Collection', bound to the ItemsControl.ItemsSource property.

The ItemsControl will always duplicate the first item added to the Collection. The ItemsControl then behaves correctly for every item added thereafter.

  • There are no cross-thread operations taking place.
  • The Collection is only updated in one place, with one call per item created.
  • The ItemsControl.ItemsPanel is a StackPanel with no virtualization.

I found a similar question. However, the proposed solution did not solve my issue.

I have a hunch that is due to using the ItemsControl inside a Popup but I can't figure out why this is happening, and only with the first item.

Any suggestions?

EDIT:

The Collection is updated in a separate singleton class. On Initialization of my View & ViewModel I create a local Collection referencing the one within the singleton. I can confirm that there are no duplicate items added to the Collection and that it does behave correctly.

Here is some sample code:

XAML:

<Popup IsOpen="{Binding ShowNotifications, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
       StaysOpen="True"
       AllowsTransparency="True">

    <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">

        <ItemsControl ItemsSource="{Binding AlarmNotifications}">

            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>

                    <StackPanel/>

                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>

            <ItemsControl.ItemTemplate>
                <DataTemplate>

                     <TextBlock Text="{Binding Exception.Message}"/>

                </DataTemplate>
            </ItemsControl.ItemTemplate>

        </ItemsControl>

    </ScrollViewer>

</Popup>

C# View-Model:

public ManagerClass Manager { get; set; }

public ObservableCollection<AlarmRegistry> AlarmNotifications { get; set; }

public bool ShowAlarmNotifications => AlarmNotifications.Any();

protected MainViewModel()
    {
        Manager = ManagerClass.Instance;

        AlarmNotifications = Manager.AlarmNotifications;

        AlarmNotifications.CollectionChanged += (sender, args) =>
        {
            OnPropertyChanged(nameof(ShowAlarmNotifications));
        };
    }

UPDATE:

I found two approaches which correct the duplication which involve opening the Popup before the first item is added:

  1. Removing the Popup.IsOpen binding and setting it to true in XAML.
  2. Setting the default value of ShowNotifications to true.

These approaches cause the application to start with the Popup open, which is an undesired behavior. However, this stops the ItemsControl duplicating the first item added. If the Popup is closed again before the first item is added, the duplication does not occur.

Now I am searching for a way to keep the Popup closed at start-up without having the first item duplicated. One approach would be trying to trick the Popup into opening and closing immediately after.

If you know why this happens, or how to counter it, please let me know.


Solution

  • The duplication occurs if the Popup is not opened before the first item is added to the collection.

    In order to correct the issue there were three steps:

    1. Add a setter and to the ShowAlarmNotifications property so it may be set directly and update the usages:

      private bool _showAlarmNotifications;
      
      
      public bool ShowAlarmNotifications
      {
          get => _showAlarmNotifications;
          set
          {
              _showAlarmNotifications = value;
              OnPropertyChanged();
          }
      }
      
      
      AlarmNotifications.CollectionChanged += (sender, args) =>
      {
          ShowAlarmNotifications = AlarmNotifications.Any();
      };
      
    2. Create a Loaded event for the View and set ShowAlarmNotifications to true:

      private void View_OnLoaded(object sender, RoutedEventArgs e)
      {
          if (DataContext is ViewModel vm)
              vm.ShowAlarmNotifications = true;
      }
      
    3. Create a Loaded event for the Popup and set ShowAlarmNotifications to false:

      private void Popup_OnLoaded(object sender, RoutedEventArgs e)
      {
          if (DataContext is ViewModel vm)
              vm.ShowAlarmNotifications = false;
      }
      

    The ItemsControl no longer duplicates the first entry and the Popup is not open when the application starts up.

    This is a messy solution and still doesn't explain why the duplication happens but it does satisfy the requirements.