1

I recently be confronted with WPF UI issue when trying to update ObservableCollection bind to a stackpanel.

I have a StackPanel in a WPF that contain custom UserControl. All these UIElement are in an ObservableCollection named "Parametres". I had them like this :

        Parametres.Clear();
        foreach(UIElement uie in SReference.UIEList)
        {
            Parametres.Add(uie);
        }

When the method is called, I clear the old parameters ObservableCollection and directly set the new one.

There is no problems with that method except that when I change the parameters list for a new one quickly, the UI miss some of them. UI doesn't skip them, it seems skip the render
enter image description here But when I change the UI size or refresh something, parameters suddenly appear
enter image description here

Is there a method for refreshing the UI or wait for UIElement render for every UIElement in my list ?

MORE DETAILS :

This application is plugin that allow user to create custom 3D Models inside a 3D design software. The goal is to cross inside different model reference and show to the user the parameters that can be edited inside the plugin.

I have an window that contain a ribbon and a main content section. This main content section change when necessary.

This is the main content section with who I have issues.
enter image description here

1- User choose a model category

2- User choose a product

3- User choose a standard reference or custom reference

4- User edit parameters

This model got many properties and some method like the one on the top. 3D model category is an ObservableCollection ComboBox content are bind to two ObservableCollection and the stackpanel that contain parameter is bind to an ObservableCollection

Every time you change something on step 1 2 or 3, ObservableCollection is clear and get (or not) some new element. There's element are generate by the main Model class.

Every UIElement in stackpanel are Custom UserControl. I have two type of these :

  • Length parameter
    enter image description here
  • Yes No Parameter
    enter image description here

this is the code behind the "SReference.UIEList"

    private List<UIElement> uieList;
    public List<UIElement> UIEList 
    {
        get
        {
            uieList.Clear();
            // On ajoute tous les éléments d'interface dans la liste.
            foreach(LengthParameterModelView parametre in parameters)
            {
                uieList.Add(parametre.lp);
            }

            string lastGroupeOption = "";

            foreach (OptionParameterModelView option in options)
            {
                if(lastGroupeOption == "" || option.NomGroupe != lastGroupeOption)
                {
                    lastGroupeOption = option.NomGroupe;

                    // Création du label du groupe d'options
                    Label l = new Label();
                    l.Margin = new Thickness(5, 0, 10, 0);
                    l.Style = ProduitAssocié.SousSectionUI.Resources["OptionLabel"] as Style;
                    l.Content = option.NomGroupe;

                    uieList.Add(l);
                }
                uieList.Add(option.ynp);
            }
            return uieList;
        }
    }

parametre.lp is UIElement generated by the ViewModel of length parameter

option.ynp is UIElement generated by the ViewModel of yes no parameter

Hope this will help.

Thomas_LCP
  • 53
  • 7
  • 2
    You should be using an ItemsControl with the UserControl in the ItemTemplate. – Clemens Sep 14 '21 at 08:30
  • This isn't related, is it: https://stackoverflow.com/q/82847/982149 ? – Fildor Sep 14 '21 at 08:33
  • @Clemens You mean changing UserControl for ItemControl ? – Thomas_LCP Sep 14 '21 at 08:46
  • 1
    @Fildor Not really because my method doesn't work async. It is located in my model section. – Thomas_LCP Sep 14 '21 at 08:46
  • _"You mean changing UserControl for ItemControl"_ - no, he said to put your UserControls in a [ItemsControl](https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.itemscontrol?view=net-5.0) parent. – Fildor Sep 14 '21 at 08:49
  • Isn't that good ? https://prnt.sc/1s5cbv5 – Thomas_LCP Sep 14 '21 at 08:56
  • 2
    See [Data Templating Overview](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/data-templating-overview?view=netframeworkdesktop-4.8). The UserControl should be declared in the DataTemplate that is used as ItemTemplate of the ItemsControl. It would thus be used to visualize a single data item from the ItemsSource collection, which would be an ObservableCollection of data item. – Clemens Sep 14 '21 at 09:03
  • That is a great idea ! In the example they bind properties from an object, I understand that. How to do it with my custom control ? All my data are already bind in a viewmodel class. This is my custom control XAML snippets : https://prnt.sc/1s5ed0o Sorry i'm a beginner :) – Thomas_LCP Sep 14 '21 at 09:17
  • @Clemens writes correctly. With a high probability, you have chosen the wrong path in the implementation of your task. But from the codes and explanations you have shown, it is very difficult to understand what kind of task in general you are trying to implement. I advise you to create a new topic in which you give a more complete explanation of the problem itself, and not just how you are trying to implement it. – EldHasp Sep 14 '21 at 10:52
  • You basically have two options. Either your control exposes a set of bindable properties (i.e. dependency properties) that are bound when the UserControl is declared in the ItemTemplate, like `` or it operates directly on the view model object passed via its DataContext. The control would then be declared without any "external" Bindings, like ``, but the elements in its own XAML would bind to the properties of the view model item. – Clemens Sep 14 '21 at 11:01
  • I will prepare that @EldHasp – Thomas_LCP Sep 14 '21 at 11:12
  • In this particular case, did the answer I suggested about moving the `Parametres` update into a separate asynchronous Dispatcher task help you? Did this solve your original problem? – EldHasp Sep 14 '21 at 11:18
  • I not even try it for now because set the method as async will be difficult for the rest of my code, I will try both of these ideas soon ;) – Thomas_LCP Sep 14 '21 at 11:24
  • There is no need to set the async modifier on the method. If after this code you do not need to handle the new content of Parametres, then the option I proposed is sufficient. If there is a need to process new content, then the code for this processing must also be placed in the task passed to the Dispatcher. You do not need to wait for the task sent to the Dispatcher to complete. – EldHasp Sep 14 '21 at 12:56
  • The code and explanations you showed are definitely not MVVM. In MVVM, the ViewModel layer doesn't need to know anything about UI elements. It is only a provider of data for View, but how the GUI works is closed information for it. To make it clearer for you, move the ViewModel and Model layers into separate projects, in which there should not even be references to assemblies with WPF elements. Naturally, in such projects, you will in no way be able to not only create UI elements, but even simply refer to them. – EldHasp Sep 14 '21 at 13:10
  • Unfortunately, based on what I understood from your explanations, there are huge mistakes in your Solution even at the stage of creating its architecture and structure. And it will be very difficult to fix them. In my opinion, it's easier to re-create a new implementation from scratch. – EldHasp Sep 14 '21 at 13:12
  • Yeah thank for the MVVM tips, I only use MV to generate UI only one time because it's difficult to do it another way for me for the moment. I agree to re-write that next time. Thanks @EldHasp – Thomas_LCP Sep 15 '21 at 06:47

1 Answers1

2

You are most likely calling the code you showed while rendering the parent.
In this case, a situation may arise when the child elements have already been evaluated and they will not be redrawn until the next rendering.

Moving this code into a separate asynchronous task for the dispatcher is likely to help you.

Supplement. If you are calling the method outside of Code Behind UserControl or Window, then you do not have a Dispatcher property.
Then you can get it from the application:

    Dispatcher.BeginInvoke((Action)(() =>
    {
        Parametres.Clear();
        foreach (UIElement uie in SReference.UIEList)
        {
            Parametres.Add(uie);
        }
    }));

I ran into a similar problem here: How to defeat a bug with Grid.Row / Column in ItemsPanelTemplate?
And a similar solution helped me.

    Application.Current.Dispatcher.InvokeAsync(() => 
    { 
        Parametres.Clear();
        foreach (UIElement uie in SReference.UIEList)
        {
            Parametres.Add(uie);
        }
    });
EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • It still feels wrong to have UIElements in an collection in the ViewModel. I agree with Clemens' suggestion of using a `DataTemplate`. Other remark: it is preferable to use `InvokeAsync()` rather than `BeginInvoke()` (and it allows removing the cast to `Action`). – Arkane Sep 14 '21 at 10:34
  • @Arkane, I totally agree with you. But from the shown code and explanation, I think it is about the implementation inside the View and the ViewModel is not involved here. It might even be better to use FreezableCollection instead of ObservableCollection here. – EldHasp Sep 14 '21 at 10:44
  • Can you give me more information to use this snippets ? Simply copy and paste that but vs intellisense return me some error with this. Thank you. – Thomas_LCP Sep 15 '21 at 07:29
  • Just copying and replacing the original code should be sufficient. You may be deleting the parenthesis, comma, or making some other minor mistake while copying. Without seeing the code in full, I can’t tell what the error is. And show the full text (and screenshot) of the error. – EldHasp Sep 15 '21 at 08:08
  • You are using this code not Code Behind your UserControl (as I thought). And the Dispatcher is a property of the DispacherObject instance (including any UI element). Get it from `Application.Current.Dispatcher.InvokeAsync (...)`. I supplemented my answer. – EldHasp Sep 15 '21 at 12:47
  • Ok, I try this, Intellisense is ok with that but UI show nothing now – Thomas_LCP Sep 15 '21 at 13:26
  • The information you provide is not enough to understand. Can you upload a solution on GiHub? Not completely, but only a minimal part relevant to the issue. – EldHasp Sep 15 '21 at 13:31
  • I don't know how to do it, I will work again on my project to make a real MVVM architecture and come back if the issue still exist. Thank you for your help ! – Thomas_LCP Sep 16 '21 at 07:57
  • Don't know how to use GitHub? If so, just create an archive of your Solution (minimized version) and provide it through some Internet resource (for example, Google Drive). – EldHasp Sep 16 '21 at 08:21