IVSoftware / multi-display-diagram

https://stackoverflow.com/a/78106143/5438626
MIT License
0 stars 0 forks source link

design for large screens #1

Open arcanisgk opened 6 months ago

arcanisgk commented 6 months ago

It works correctly in terms of objectives and logic, just a small hiccup in the design.

In this case screen #3 has 2340 x 1080 scale 175% (it is a mobile device that is used as a secondary screen through spacedesk)

The button is separated from the other two.

image

current layout and setting of #3 display

image

The truth is that in my SO question, it is not my objective that the buttons are to scale (it sounds interesting but it can be difficult), what interests me is to show a 160 X 100 button where the monitors are and allow clicking to select.

IVSoftware commented 6 months ago

Thanks for the feedback! I understand the issue but unfortunately have no way to reproduce it so I will speculate that it has to do with the relative screen densities that need to be taken into account in the scaling factor. Once this is done I predict that the sizes would be "apples-to-apples".

arcanisgk commented 6 months ago

If you want I can do tests, but I would need you to explain or tell me how to introduce those additional variables to the calculation, which I think is worth it in case someone uses 4K monitors with different sizes... that's why I thought that I would represent them with buttons. 160X100 (H) or 100X160 (V) would be easier, hahahahaha.

IVSoftware commented 6 months ago
image

I do love a challenge. What I've done so far is change the display resolution of my L/H monitor. It's enough to show the artifact.

arcanisgk commented 6 months ago

ok but the spaces between monitors are normal?

I don't care about the size of the monitors hahahahaha...

IVSoftware commented 6 months ago

We'd prefer to see it mimic the System version, yes?

image
arcanisgk commented 6 months ago

sure... if possible of course :)

IVSoftware commented 6 months ago

In between trying to get my "real job" done, I managed to mess with this a little and remove the gaps cosmetically making it look closer to the System version. I pushed it to a separate branch if you want to test whether it makes any diff. You said the app main window still opens in the mobile screen regardless, though? At least it "should" since we're giving it the real (not scaled for viewing) Screen.WorkingArea metrics.

image

Anyway, I hope this has moved you closer to your best case scenario. Cheers.


Appended to OnLoad


            // Address gaps
            var buttonArray = workspacePanel
                .Controls.OfType<Button>()
                .ToArray();
            var bottomArray =
                buttonArray
                .OrderBy(_ => _.Top).ThenBy(_ => _.Left) 
                .ToArray();
            for (int i = 1; i < bottomArray.Length; i++)
            {
                var gapY = bottomArray[i].Top - bottomArray[i - 1].Bottom;
                if (gapY > 0)
                {
                    for (int j = i; j < bottomArray.Length; j++)
                    {
                        bottomArray[j].Top -= gapY;
                    }
                }
            }

            var leftArray =
                buttonArray
                .OrderBy(_ => _.Left).ThenBy(_ => _.Top)
                .ToArray();
            for (int i = 1; i < leftArray.Length; i++)
            {
                var gapX = leftArray[i].Left - leftArray[i - 1].Right;
                if (gapX > 0)
                {
                    for (int j = i; j < leftArray.Length; j++)
                    {
                        leftArray[j].Left -= gapX; 
                    }
                }
            }
            var widthB4 = workspacePanel.Width;
            var heightB4 = workspacePanel.Height;
            // Trim
            maxRight = buttonArray.Max(btn => btn.Right);
            maxBottom = buttonArray.Max(btn => btn.Bottom);
            workspacePanel.Size = new Size(maxRight, maxBottom);

            scaleX = widthB4 / (double) workspacePanel.Width;
            scaleY = heightB4 / (double)workspacePanel.Height;
            scale = Math.Max(scaleX, scaleY);

            var trimmedWidth = workspacePanel.Width;
            var trimmedHeight = workspacePanel.Height;

            workspacePanel.Width = (int)(trimmedWidth * scale);
            workspacePanel.Height = (int)(trimmedHeight * scale);

            foreach (Button btn in workspacePanel.Controls)
            {
                btn.Width = (int)(btn.Width * scale);
                btn.Height = (int)(btn.Height * scale);
                btn.Location = new Point((int)(btn.Location.X * scale), (int)(btn.Location.Y * scale));
            }
arcanisgk commented 6 months ago

Yes, in theory you should be able to open/move the app on any primary or secondary screen... I'm testing the script.

I am seeing that coincidentally it has something that I had not thought about before, it seems that it may not be compatible with my development, I should make modifications on my part to adapt it and make it work, and that is that you are using the MainForm as an initial plan to also show the selector, it is like a SPA (I don't know what they call this in software)...

Personally, I had planned to call displaySelector on MainForm load, with the idea of presenting displaySelector in a completely separate form, so I could port it to any development application.

Another way would be through a DLL, the truth is I still don't know how to use them, but convert this utility into a DLL that can be integrated into any development.

and facilitate the call to her with something like this:

DisplaySelector.Init();

IVSoftware commented 6 months ago

Looks may be deceptive that way; the DisplaySelectorForm has no real dependencies on MainForm whatsoever and is a top-level control. It could easily be in a separate shared DLL without issues, with one minor change in the screenButton.Click += lambda Owner is Form appForm (or even Owner is Control control) instead of Owner is MainForm mainForm which I should have done anyway. The only reason I did it in "splash screen" style is that it's one of those things (similar to a login) that you might want to get settled before making the main wnd visible just for aesthetics.

arcanisgk commented 6 months ago

So most likely I have an incorrect structure in my development, this happens:

  1. When opening the application I call MainForm, but do not show it instead, I make it invisible and call selectorDisplay.

  2. When the user uses selectorDisplay to choose the screen, I will move the hidden MainForm to that chosen screen.

  3. Instead of showing MainForm, I will call a form through a dialog with a Welcome and legal notice that the user must accept.

  4. When accepting the welcome form, it will not show MainForm, but rather a form should be shown that will evaluate if there are any prerequisites installed.

  5. when the prerequisites have been evaluated and are installed, then yes I will Show in Mainform of my application.

For me, the screen selector is the first of 3 or 4 previous steps that the user or the application itself must do before reaching the main form.

arcanisgk commented 6 months ago

i have try to update the code and test your news features from the other branch and i get this:

image

IVSoftware commented 6 months ago

What happens if you just clone and run the remove-gaps branch directly, without copying anything into your project?

IVSoftware commented 6 months ago

I was also replying to your previous...


I believe I understand what you're saying, and there's nothing really incorrect about that flow but here is the subtle, nuanced thing: By the time you get OnLoad or even HandleCreated your main form is visible already. So for example if you're trying to make sure the user logs in before they're allowed to see anything on your main form it's just too bad because the form is now visible for a moment before you'll have the chance to hide it again. And this is the reason for intercepting and overriding SetVisibleCore because here you can prevent it from ever being shown, even for that instant.

But doing that makes a new problem. Ordinarily the native window handle is created during the process of showing it. So there's now Handle and without a handle there's no BeginInvoke and without a begin invoke you might be blocking your main form constructor with a modal dialog which is very, very bad! 😮. The solution is:

  1. Intercept SetVisibleCore making sure to disable the main form from being shown until you have set the boolean indicating that user is authorized and has access to the form.
  2. Force the native handle to create, using _ = Handle.
  3. Post the action to show the modal splash screen (or consecutive screens as you mention) by putting it at the end of the UI message queue using BeginInvoke. This allows the C'Tor to complete.

    Hope this is makes sense. I wish someone had explained it to me when I was trying to figure it out...

arcanisgk commented 6 months ago

What happens if you just clone and run the remove-gaps branch directly, without copying anything into your project?

same view ...

image

IVSoftware commented 6 months ago

Comment out from this line down: var widthB4 = workspacePanel.Width;...

arcanisgk commented 6 months ago

I was also replying to your previous...

I believe I understand what you're saying, and there's nothing really incorrect about that flow but here is the subtle, nuanced thing: By the time you get OnLoad or even HandleCreated your main form is visible already. So for example if you're trying to make sure the user logs in before they're allowed to see anything on your main form it's just too bad because the form is now visible for a moment before you'll have the chance to hide it again. And this is the reason for intercepting and overriding SetVisibleCore because here you can prevent it from ever being shown, even for that instant.

But doing that makes a new problem. Ordinarily the native window handle is created during the process of showing it. So there's now Handle and without a handle there's no BeginInvoke and without a begin invoke you might be blocking your main form constructor with a modal dialog which is very, very bad! 😮. The solution is:

  1. Intercept SetVisibleCore making sure to disable the main form from being shown until you have set the boolean indicating that user is authorized and has access to the form.
  2. Force the native handle to create, using _ = Handle.
  3. Post the action to show the modal splash screen (or consecutive screens as you mention) by putting it at the end of the UI message queue using BeginInvoke. This allows the C'Tor to complete.

Hope this is makes sense. I wish someone had explained it to me when I was trying to figure it out...

ok I think I was wrong, I had this before modifying everything you have implemented:

        public Main()
        {

            ElevatedPrivileges.CheckElevated();
            InitializeComponent();
            this.Visible = false;
            DisplaySelector.Init(); // show form dialog to select display
            Wellcome.Agreement();
            Requirement.Deploy();
            this.Visible = true;
        }
arcanisgk commented 6 months ago

var widthB4 =

Work fine in your demo (With comments):

image

IVSoftware commented 6 months ago

progress yay. I thought of something else I might have jacked up the first time. Give me a moment to test...

IVSoftware commented 6 months ago

Yeah one issue is that I think we should be using Bounds not WorkingArea. Still figuring out where to go from there (this has become one of those things where I just want the humans to win, not the computer LOL).

arcanisgk commented 6 months ago

Can you imagine how I felt when I showed you how far I had come XD.

I'm still thinking about the correct way to implement it in my project, since it is a desktop application without login.

arcanisgk commented 6 months ago

@IVSoftware have you made any changes on branch?

arcanisgk commented 5 months ago

@IVSoftware i get this working:

Captura de pantalla 2024-03-18 115442