dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.04k stars 1.17k forks source link

Setting System.Windows.Media.Pen.DashStyle exhibits unreliable behavior #6355

Open NWoodsman opened 2 years ago

NWoodsman commented 2 years ago

Setting System.Windows.Media.Pen.DashStyle to a value changes the UI once and not again.

The code sample includes the following code:

            switch (Counter)
            {
                case 0:
                    LineElement.Pen.DashStyle = new DashStyle(new double[] { 4, 6 }, 0);
                    Counter++;
                    break;
                case 1:
                    LineElement.Pen.DashStyle = DashStyles.Solid;
                    Counter++;
                    break;
                case 2:
                    LineElement.Pen.DashStyle = new DashStyle(new double[] { 4, 6 }, 0);
                    Counter++;
                    break;
                case 3:
                    LineElement.Pen.DashStyle = DashStyles.Solid;
                    Counter++;
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
            }

In the above code extracted from my gist, the GUI changes at case 0 correctly and draws a dashed line. Then it fails to update at every other case.

The code copied into a .Net 4.8 WPF app exhibits the same behavior.

Actual behavior:

The GUI changes exactly once when the Pen.DashStyle dependency property updates. Then it stops updating.

Expected behavior:

I expected that setting Pen.DashStyle more than once to new values would behave like a dependency property (since it is).

Minimal repro:

Create new WPF .Net6 app, call it WpfApp1. Replace all of the code in MainWindow.xaml and MainWindow.xaml.cs with the code in the following gist files: https://gist.github.com/NWoodsman/eccbc3c3164bc0691c6841d2836e6684 Run the app and click the button repeatedly.

NWoodsman commented 2 years ago

The first change to the default value of a mutable collection property (e.g. GeometryGroup.Children) will promote the property value from a default value to a local value. This is technically a sub-property change because the collection was changed and not a new collection set (GeometryGroup.Children. Add versus GeometryGroup.Children = myNewChildrenCollection). However, we never marshalled the default value to the compositor. If the property changes from a default value, the new local value needs to be marshalled to the compositor. We detect this scenario with the second condition e.OldValueSource != e.NewValueSource. Specifically in this scenario the OldValueSource will be Default and the NewValueSource will be Local.

I found this comment in the System.Windows.Media.Pen class in the callback for the DashStyle dependency property change. It seems that it might apply to my problem, as it seems to refer to the first setting of the dependency property being unique, however I cannot find any public explanation of this though. If anyone is more familiar with WPF, can you shed some light on this?

NWoodsman commented 2 years ago

I stepped through the managed code all the way back to my app. No errors or exceptions, it simply fails to redraw the UI.

NWoodsman commented 2 years ago

@gurpreet-wpf @lindexi

I have refined my gist to demonstrate more undesirable broken behavior, this time showing that the basic default Dash Styles found in DashStyles static class are broken. i.e. DashStyles.Solid, DashStyles.Dash, DashStyles.Dot

Step 1 - Setting DashStyles.Dash then DashStyles.Dot does not entirely replace the pattern with Dot , the line becomes a mix of both properties.

And, if the order is reversed, setting DashStyles.Dot then DashStyles.Dash does not entirely replace the pattern with Dash , the line becomes a mix of both properties, and the resulting line looks even different than Step 1, meaning that the line will render entirely different depending on the order.

https://gist.github.com/NWoodsman/eccbc3c3164bc0691c6841d2836e6684

Create a new WPF app called WpfApp1, copy all the code from the MainWindow.xaml gist into MainWindow.xaml and copy all the code from MainWindow.xaml.cs gist into MainWindow.xaml.cs.

In the switch block of MainWindow.xaml.cs, changing the case 1 to 2 and 2 to 1 demonstrates an entirely different appearance dashed line.