Skip to content

Convert WpfPlot to CustomControl#2526

Merged
swharden merged 24 commits intoScottPlot:mainfrom
KroMignon:CustomControl
Apr 3, 2023
Merged

Convert WpfPlot to CustomControl#2526
swharden merged 24 commits intoScottPlot:mainfrom
KroMignon:CustomControl

Conversation

@KroMignon
Copy link
Contributor

@KroMignon KroMignon commented Mar 30, 2023

Purpose:

Use Control as base class for WpfPlot instead of UserControl to enable customization. (cf #2509)

  • add property to show / hide legend
  • add property to define legend position
  • add property to select traces palette
  • add property to select plot style
  • remove dead code

It is now possible to select WpfPlot palette and style directly from XAML

image

image

image

This control replace the previous without any change on existing XAML or C# code.

NOTE I could not change the ScottPlot5 version, because I am not able to load the solution (cf #2525)

Fabrice Mousset added 2 commits March 30, 2023 14:54
- add propertie to show / hide legend
- add propertie to define legend position
- add propertie to select traces palette
- add propertie to select plot style
- remove dead code
@swharden swharden linked an issue Mar 30, 2023 that may be closed by this pull request
@swharden
Copy link
Member

Hi @KroMignon, thanks for this PR! I won't be able to review it thoroughly until tonight, but I'm happy to see you figured out the PR process and the tests are passing and I look forward to merging this soon! 🚀

I have one bit of feedback about a feature that was added:

It is now possible to select WpfPlot palette and style directly from XAML

I'm extremely hesitant to allow plot customizations from XAML. There are potentially thousands of little customizations that could be added (e.g., axis tick mark length), and if we allow some it will open the door for others to request more. Also, if we add XAML customizations to WPF then I would think we need to add the same to WinUI, Avalonia, Eto, Blazor, etc., and the maintenance burden grows as the number of supported controls grows. Although I think what you did here with properties and XAML is elegant and would be an ideal solution if ScottPlot were a WPF-only control, for the sake of maintainability I would feel better not customizing plots using styling, but instead interacting with the Plot object as demonstrated by the cookbook.

However, I custom XAML customizations are the type of thing that will be enabled by the new inherit-and-extend functionality that the core of this PR now allows 😉

Thanks again for your work on this, and I'm looking forward to hearing your thoughts!
Scott

Fabrice Mousset added 2 commits March 30, 2023 16:35
- move style and palette enumeration into base classe
@KroMignon
Copy link
Contributor Author

Hello @swharden , creating PR was easier as expected 🤓

I understand your arguments about the plot customization. I was not really aware about all the other backends. It was something I'd like to have for my purpose, but I could implement it on application side with this modification 😄

It would be great if the enumeration + dictionary in Style and Palette could be added in ScottPlot, even with other names. This would simplify for me the customization of WpfPlot.

@KroMignon
Copy link
Contributor Author

Good morning @swharden

I can now open the ScottPlot 5 solution and I have started to look at the code. I have first to understand how the WPF control has been design before I implement my modification. SkiaSharp is new to me.
I will look at this more closely this week-end, I've got some ideas how it could be more WPF / MVVM "friendly" but it needs some tests.

Is it okay if I do some modification on ScottPlot.Control.Interaction, I am not sure I have to, but maybe 🤔

swharden added 5 commits April 1, 2023 22:36
revert changes not central to the primary goal of ScottPlot#2526

the WPF control is not functioning as expected -- it does not display plots in the cookbook or sandbox apps
revert changes not central to the primary goal of ScottPlot#2526
@swharden
Copy link
Member

swharden commented Apr 2, 2023

Hi @KroMignon, thanks again for your work on this!

To try to keep this PR clean and isolated to a single purpose I reverted some of your changes related to styling enhancements. This makes it easier to do a line-by-line evaluation of https://github.com/ScottPlot/ScottPlot/pull/2526/files

I don't have a lot of experience with WPF. Can you advise us about the purpose of Generic.xaml and whether it is required? It seems like we're pretty close to having a XAML-free control which makes me really happy. Is this functionality required and/or could it be performed programmatically?

Thanks for your input! I think we're getting pretty close to being ready to merge this in 🚀

EDIT: After this is merged we can revisit styling and palette improvements in a separate issue/PR

@KroMignon
Copy link
Contributor Author

Hi @KroMignon, thanks again for your work on this!

To try to keep this PR clean and isolated to a single purpose I reverted some of your changes related to styling enhancements. This makes it easier to do a line-by-line evaluation of https://github.com/ScottPlot/ScottPlot/pull/2526/files

That's fine for me 👍

I don't have a lot of experience with WPF. Can you advise us about the purpose of Generic.xaml and whether it is required? It seems like we're pretty close to having a XAML-free control which makes me really happy. Is this functionality required and/or could it be performed programmatically?

A WPF Control requires at least a default style, which will be loaded by OnApplyTemplate(). In the style, we will define the content of the control. If there is no default style, the component will be empty/transparent.
For this control, the style contains a picture (to show the graph) and a label (to show the error messages). In fact, it is the same structure as the previous UserControl. The name of those items (picture and label) is important to be able to extract them from the style, with Template.FindName().

The difference with a UserControl, is that now it is possible to customize the control, which is not possible with a UserControl (cf. https://www.wpftutorial.net/CustomVsUserControl.html)

Thanks for your input! I think we're getting pretty close to being ready to merge this in 🚀

Your welcome 😉

EDIT: After this is merged we can revisit styling and palette improvements in a separate issue/PR

@swharden
Copy link
Member

swharden commented Apr 2, 2023

Thanks for the clarification @KroMignon! I read some more information about custom controls and WPF templates and it makes a lot more sense now - thanks for pointing me in the right direction.

I plan to release a new package today on NuGet, but I'm still hesitant to merge this in. I found one bug that I'll try to fix now, and depending on how easy it is to correct and/or how many other bugs I find, I may push this back until I have more time to review and test it thoroughly.

Here's the bug I found this morning. I suspect it relates to events not being handled as expected...

<Window x:Class="WpfApp.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:WpfApp"
        mc:Ignorable="d"
        xmlns:ScottPlot="clr-namespace:ScottPlot;assembly=ScottPlot.WPF"
        Title="ScottPlot Sandbox - WPF" 
        WindowStartupLocation="CenterScreen"
        Height="450"
        Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <ScottPlot:WpfPlot Name="WpfPlot1" Grid.Row="0"/>
        <ScottPlot:WpfPlot Name="WpfPlot2" Grid.Row="1"/>
    </Grid>
</Window>
using System.Windows;

namespace WpfApp
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            WpfPlot1.Plot.AddSignal(ScottPlot.DataGen.Sin(51, 2));
            WpfPlot2.Plot.AddSignal(ScottPlot.DataGen.Cos(51, 2));

            WpfPlot1.Refresh();
            WpfPlot2.Refresh();

            WpfPlot1.Configuration.AddLinkedControl(WpfPlot2); // update plot 2 when plot 1 changes
            WpfPlot2.Configuration.AddLinkedControl(WpfPlot1); // update plot 1 when plot 2 changes
        }
    }
}
main branch this PR
image image

demonstrate issue described in ScottPlot#2526
@swharden
Copy link
Member

swharden commented Apr 2, 2023

Maybe this is a niche case, because this works... so I'm guessing it's some type of difference in window size evaluation inside the initializer? The size of the control is evaluated to determine how large to make the plot area.

using System.Windows;

namespace WpfApp
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Loaded += (s, e) =>
            {
                WpfPlot1.Plot.AddSignal(ScottPlot.DataGen.Sin(51, 2));
                WpfPlot2.Plot.AddSignal(ScottPlot.DataGen.Cos(51, 2));

                WpfPlot1.Refresh();
                WpfPlot2.Refresh();

                WpfPlot1.Configuration.AddLinkedControl(WpfPlot2);
                WpfPlot2.Configuration.AddLinkedControl(WpfPlot1);
            };
        }
    }
}

However, it's these type of unexpected changes in behavior that concern me considering how many existing users have code in the wild that depends on the functionality of WpfPlot to remain stable 😅

@swharden
Copy link
Member

swharden commented Apr 2, 2023

Looking at the original WpfPlot control, OnSizeChanged is not invoked by the main window or the image, but the grid... I wonder if this difference is what is causing the discrepancy

<Grid x:Name="MainGrid" SizeChanged="OnSizeChanged">

I'm probably going to have to put this down for a while because I don't want it to consume a disproportionate amount of time considering the other issues I'm trying to clear today 😅

Let me know if you have any thoughts or suggestions for how to track this down, otherwise I'll pick it up again in a day or two! Thanks again for your work on this

@KroMignon
Copy link
Contributor Author

Looking at the original WpfPlot control, OnSizeChanged is not invoked by the main window or the image, but the grid... I wonder if this difference is what is causing the discrepancy

<Grid x:Name="MainGrid" SizeChanged="OnSizeChanged">

I am not a WPF specialist, so maybe I am wrong. You cannot use the OnSizeChanged from the Image itself, because it depends on the load image.
I have used the size of the control itself, because the Grid should follow the size of the control. It looks like I missed something 😞.

I'm probably going to have to put this down for a while because I don't want it to consume a disproportionate amount of time considering the other issues I'm trying to clear today 😅

Let me know if you have any thoughts or suggestions for how to track this down, otherwise I'll pick it up again in a day or two! Thanks again for your work on this

As I am really new to PR, could you please tell me how I can import the changes you have done into my clone (I create a branch to test the changes)?
Or what would be the best way to continue with your changes?
I would look at this more closely tomorrow.
Perhaps you should way before merge this change to the main branch.

@swharden
Copy link
Member

swharden commented Apr 2, 2023

It looks like I missed something

No worries! There are tons of little corner cases and it's hard to catch everything. The things that I frequently miss are when I make a change that breaks support for display scaling, because that's really difficult to test 😅

As I am really new to PR, could you please tell me how I can import the changes you have done into my clone (I create a branch to test the changes)?

I recommend using the GitHub Desktop app. It's really easy to use (command line git and git inside Visual Studio can both be pretty confusing)

https://desktop.github.com/

Using the GitHub desktop you can open this repository, switch to the branch for this PR, and click "fetch" or "pull" to bring the latest code onto your computer.

image

I recognize dealing with multi-author branches and pull requests is pretty confusing at first, but once you figure it out once it gets a lot easier! 🚀

@KroMignon
Copy link
Contributor Author

Let me know if you have any thoughts or suggestions for how to track this down, otherwise I'll pick it up again in a day or two! Thanks again for your work on this

I think I found what's going wrong after reading Microsoft documentation.
The SizeChanged event is not the best choice, it should be replaced with LayoutUpdated, because every time LayoutUpdated is emitted ActualHeight and ActualWidth are valid. This is not always the case with SizeChanged event.

@swharden
Copy link
Member

swharden commented Apr 2, 2023

The SizeChanged event ... should be replaced with LayoutUpdated

I gave this a quick try, but the control crashes maybe because of some type of infinite loop 😅

- update WpfPlot to override properties to handle size change and mouse events
- update Backend::Resize() to avoid unneeded updates
- update Plot::Add(IPlottable) to force axes rescalling on first added signal (maybe a bad idea)
- revert Plot..Add() change
- fix WpfPlot initialisation: always initialize backend with 1x1 pixel and resize it at least during OnApplyTemplate().
@KroMignon
Copy link
Contributor Author

KroMignon commented Apr 3, 2023

Hello @swharden,

I made some changes to try to fix the issue with WPF Sandbox app. Now it looks good for me 🚀 😄

Hope it is okay for you:

  • Backend: little change in Resize() to avoid creating a new Bitmap when resizing is done with same size
  • WpfPlot:
    • override Control function instead of using events
    • always create Backend instance with 1x1 size to ensure a Bitmap will be created and then resized. This solves the issue.

@swharden
Copy link
Member

swharden commented Apr 3, 2023

Fantastic work @KroMignon! That was a complicated behavior to figure out. I'm excited to try this out and merge it in! 🚀

@swharden swharden enabled auto-merge April 3, 2023 22:35
@swharden
Copy link
Member

swharden commented Apr 3, 2023

This works great - merging it in now!

I made one small change so the scroll wheel respects direction: 8d3c065

I learned a lot about WPF by studying the topics discussed above - thanks again for the initial suggestion and creating the PR!

@swharden swharden merged commit ec26caf into ScottPlot:main Apr 3, 2023
@KroMignon KroMignon deleted the CustomControl branch April 4, 2023 05:32
@KroMignon
Copy link
Contributor Author

Very good news this morning 🎉, I am excited to use new ScottPlotV4 release !
I am very happy I could help, and will starts soon a new PR for ScottPlotV5, this should be done faster... I hope 🙈

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WpfPlot: UserControl → CustomControl

3 participants