Results 1 to 14 of 14

Thread: Building winforms application on laptop, for large screen monitors

  1. #1

    Thread Starter
    PowerPoster MMock's Avatar
    Join Date
    Apr 2007
    Location
    Driving a 2018 Mustang GT down Route 8
    Posts
    4,475

    Building winforms application on laptop, for large screen monitors

    Probably in the wrong forum...again. But no one yelled at me last time so here goes.

    I am working at home today and tomorrow on my laptop. It has a smaller screen than my desktop monitors at work. I am developing a windows application and using all the real estate I have available on my large monitors. My users are testing (they also run on their desktops) and I am making fixes which means I want to rebuild and publish. But when I do that, by default the Windows forms sizes are going to be small and the buttons on the bottom of the screen will just "fall off" because I'd be building on this laptop.

    Yes, I am going to work on making my forms sizeable and having the controls grow and shrink, but I'd kind of like to get this logic fix built now so users can continue.

    Am I understanding the cause of the problem correctly and is there anything I can do? Other than go into the office? My daughter had her four wisdom teeth pulled and doesn't really want to be home alone .
    There are 10 kinds of people in this world. Those who understand binary, and those who don't.

  2. #2
    PowerPoster jdc2000's Avatar
    Join Date
    Oct 2001
    Location
    Idaho Falls, Idaho USA
    Posts
    2,393

    Re: Building winforms application on laptop, for large screen monitors

    I have not tried this, but maybe using the command line compile options would work:

    https://www.google.com/?gws_rd=ssl#q...mpile&spf=1246

  3. #3

    Thread Starter
    PowerPoster MMock's Avatar
    Join Date
    Apr 2007
    Location
    Driving a 2018 Mustang GT down Route 8
    Posts
    4,475

    Re: Building winforms application on laptop, for large screen monitors

    Thanks, I will take a look at that. I also tried setting the form's ClientSize property in the constructor, which should override what the designer changes it to if I open the form in designer on my laptop. I deployed and asked someone if they could give it a quick test, but I guess they already went home for the day. Of course, I cannot test it myself.
    There are 10 kinds of people in this world. Those who understand binary, and those who don't.

  4. #4
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Building winforms application on laptop, for large screen monitors

    This is a hobby area I've been away from for far too long. I'm going to make a relatively small post here, but I spent about an hour and a half building a huge tutorial that walks through the basics, then designs something like the MessageBox dialog such that it can size itself, let the user resize it, look sane on any size monitor, AND as a bonus includes a discussion of how to make it look fine on high-density displays like a Surface, too.

    But it's about 100 paragraphs, so I figure you'd like a quicker answer.

    If the problem is DPI scaling, I haven't found a foolproof answer in WinForms. Devices like the Surface do a lot of work to try and make WinForms applications look good. It has to do this because while 48 pixels is about half an inch and a good default button height on most standard monitors, it's only about a quarter of an inch tall on the Surface's high-density display. Worse: font sizes are defined in points, so a 12-point font is about 16 pixels tall on a standard monitor, but 38 pixels tall on the Surface! See how that can cause problems? Windows Forms has some built-in tools to try and account for that, but they work inconsistently and usually make a mess of programs.

    If the problem is "I want my controls to reposition themselves and resize themselves on a larger monitor, the problem isn't DPI scaling", boy can I ever help.

    First, read about the TableLayoutPanel, FlowLayoutPanel, and DockContainer. These panels can be used to make layouts that stretch and shrink in various ways as the form is resized. They can cover at least 90% of most cases, so spending a few days playing with them might help you.

    You also need to be innately familiar with how the Anchor and Dock properties work. They're key to using most of those layout containers.

    When the containers don't work, you use good old fashioned 2D geometry. This is what we had to do before the layout containers showed up. The bulk of my giant tutorial was devoted to demonstrating this, but it involves thinking things like:
    To make a MessageBox-like dialog fit any size, I can do some calculations. The label has to fit the width of the form minus some padding. The text should fit, so I have to ask Windows to measure how tall it might be and do something else if it's too big to fit on the monitor. When I know how big the label needs to be, I can place the buttons a little underneath the bottom of the label, horizontally centered. Finally, I need to resize the form to fit the label's height plus buttons plus all padding.
    There's not a lot of tricks to it, but it only feels easy after honing it for a few months.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  5. #5
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Building winforms application on laptop, for large screen monitors

    Here's the tutorial, in all its glory, because I have no life when my wife is out of town.

    Supporting different monitor sizes involves a handful of tricks if you can't use a layout panel.

    The first trick is learning about the monitor your application is on. This gets really tricky if you want to support multiple monitors, let's stick to one. Screen.PrimaryScreen will get you the only monitor on a one-monitor system. Its Bounds property tells you the size of the screen, but you generally want to use WorkingArea instead because it subtracts the taskbar and any other such permanent fixtures. If you're trying to make your form fill the screen, this will give you a good idea of the target size.

    Next, you need to be aware that a form's Size is not actually what you think it is. The area where you can put controls is called the "client area", and is represented by the ClientSize property. It excludes the borders and title bar, which are called the "non-client area". So setting Size to the screen's working area will make your form fill the screen, but you need to ask for ClientSize to know how much room you have for your controls.

    Now you need to actually lay them out. To do that, it helps to think of controls in a relative fashion and get used to 2D geometric math. It usually helps if you can get a Rectangle to represent things, becuase it has X, Y, Width, Height, AND Top, Bottom, Left, Right. That's crucial for aligning things.

    Sometimes you need to know how to measure text, too. Labels can auto-size, but that doesn't help you if you need to make a GroupBox big enough to hold all of the text in a label.

    From there, it's a matter of defining where your controls go relative to the edges of the client area, then potentially relative to the controls you've already laid out. Here's an example. Think about the standard MessageBox. It has an "OK" and "Cancel" button (sure, you can change the text, let's ignore that) and a Label that displays a lot of text above the buttons. It sizes itself to try and display all of the text, and centers the buttons. How might you pull that off?

    Well, we have to start with the label. It needs to be tall enough for the text to fit, and we want to make the form bigger to allow for it. It can't be taller than the screen, which means we can't use a size taller than the height of the screen, minus the height of the buttons, minus the non-client area and any padding we want to add. Whew.

    So I'll start by carving out the top-left and width information for the label. The dialog width seems to be about 25% of the screen's width. The text usually has some padding to the left and right, I'm not being too scientific but it seems about half an inch, which is generally about 48px. (Let's not talk about high-density displays.) It seems to use about half that to distance itself from the top. So our X and Y coordinates will be (48, 24). Width? That takes some math.
    Code:
    int desiredWidth = (int)Math.Floor(Screen.PrimaryScreen.WorkingArea.Width * 0.25);
    int textPaddingX = 48;
    int textPaddingY = 24;
    
    lblMessage.Location = new Point(textPaddingX, textPaddingY);
    
    // I like to draw diagrams above my size calculations to help me
    // sanity check:
    // || p | <text> | p ||
    int labelWidth = desiredWidth - (2 * textPaddingX);
    For height, we have to make a measurement. We have a width for the proposed size, but not a height yet. I could use the height of the buttons and ask the system to tell me about the non-client area's size, but instead I'm going to decide I don't like a dialog to be more than 90% of the screen's height, then estimate down to 70% to allow some wiggle room. Given a width and height, I have to tell the string how to render: I want it left aligned and I want it to word-wrap. That will give me the final height for the label.
    Code:
    int maximumLabelHeight = (int)Math.Floor(Screen.PrimaryScreen.WorkingArea.Height * 0.7);
    Size maximumLabelSize = new Size(labelWidth, maximumLabelHeight);
    
    var formatting = TextFormatFlags.Left | TextFormatFlags.WordBreak;
    Size calculatedLabelSize = TextRenderer.MeasureString(message, this.Font, maximumLabelSize, formatting);
    
    // Windows might indicate the string just can't fit, so we need to choose a height.
    bool canStringFit = calculatedLabelSize.Height <= maximumLabelHeight;
    int finalLabelHeight = canStringFit ? calculatedLabelSize.Height : maximumLabelHeight;
    
    lblMessage.Size = new Size(labelWidth, finalLabelHeight);
    Whew! Laying out the buttons is a lot easier with that out of the way.

    Vertically speaking, they're just a little bit of padding underneath the text label. My eye tells me it's about 10-15 pixels, I like 10.
    Code:
    int buttonPaddingY = 10;
    int buttonY = lblMessage.Size.Bottom + buttonPaddingY;
    Placing them horizontally is easy, but takes some math. To center something, you need to start with its Left (X) coordinate touching the center line of the Form, which is (Width / 2). Then you need to subtract enough from X so that its own center line (also Width / 2) is touching the Form's center line. The trick here is we aren't centering one button, we're centering two plus some padding between them, and we have to set two separate X coordinates. But we can cheat: if we properly place one button, we can use its coordinates to set the other one easily.
    Code:
    int centerX = this.ClientSize.Width / 2;
    // Protip: lay out your variables in the same order they appear on the screen.
    // |   OK    | p |   CANCEL   |
    int totalButtonWidth = okButton.Width + buttonPaddingX + cancelButton.Width;
    int buttonCenterX = totalButtonWidth / 2;
    
    int okButtonX = centerX - buttonCenterX;
    int cancelButtonX = okButton.Bounds.Right + buttonPaddingX;
    
    okButton.Location = new Point(okButtonX, buttonY);
    cancelButton.Location = new Point(cancelButtonX, buttonY);
    We're almost done! The dialog box starts with a default width and height. The Label might be bigger than the height allows, so we need to set the ClientSize to something that can contain the label, the buttons, and all vertical padding between them. Again, diagrams help:
    Code:
    int bottomPadding = 10;
    // (top) || bigPadding | <label> | p | <buttons> | p || (bottom)
    int totalHeight = textPaddingY + lblMessage.Height + buttonPaddingY + okButton.Height + buttonPaddingY;
    
    this.ClientSize = new Size(this.ClientSize.Width, totalHeight);
    If you put that in the Shown event handler, you'll get a dialog box that (hopefully) works the way I've described it on any size monitor.

    Resizable windows take some extra work. Let's say this dialog box lets the user resize it. If text can't fit in the label, it'll just not fit, that's the user's problem. Obviously we have to redo layout when the form's size changes, so you need to handle an event and consider how each control changes.

    One note: if you set the Anchor properties properly, all of the work's already done for you. It's good to learn how they work. When you anchor a control to an edge, you're saying, "If the form resizes, this control should stay the same distance from this edge." If you anchor to opposite edges, the control will grow as the form resizes. So it'd be appropriate to set the message label's Anchor to all four edges in the dialog, since it needs to grow and shrink with the form but maintain padding distances. The buttons just need to anchor to Bottom. If you anchor them to Left or Right, they'll move apart from each other as the form grows and overlap each other if it shrinks. Oops.

    If you don't or can't use Anchor, you have to re-evaluate all of the relationships above with one difference: now that the user is choosing the size, we can't measure the string to get the ideal height for lblMessage. It's just going to have to fit in the space it has. Its layout code for height would be something like:
    Code:
    // (top) || bigPadding | ??? | p | <buttons> | p || (bottom)
    int labelHeight = ClientSize.Height - textPaddingY - buttonPaddingY - okButton.Height - buttonPaddingY;
    Homework: you can use these relationships to set MinimumSize and MaximumSize bounds for forms. Obviously if the user scrunches the dialog to less than 2 * textPaddingX there's no room for the label, but also being smaller than totalButtonWidth is ugly too. Just make sure to set them AFTER adjusting your form's size, I'm not sure what happens if the form's "out of bounds" when you set those.

    Whew. OK. I don't know why I wrote all that, it felt good though. More complex forms just have more relationships. A lot of auto-sizing can be handled by Anchor, Dock, and containers like TableLayoutPanel, but with some elbow grease you can do it yourself.

    And it's a quarter of the work to handle DPI scaling. While I'm here I may as well mention that.

    WPF uses "device-independent units" instead of pixels. There are 96 units per inch, and it uses these everywhere WinForms would use pixels, even for font sizes. So if I set a font size of 16, I get text that's 1/6" on any device. Nice! Supporting DPI scaling involves working with that kind of unit when you're in Windows Forms.

    To get there, you have to know the monitor's DPI. Unfortunately that's not a property of the Screen class, it's a big omission. The only way I know to get this is to have a Graphics object, which means either calling CreateGraphics() or overriding your form's OnPaint(). OnPaint() is cleaner, but can happen too late to run your layout code. So CreateGraphics() it is.

    There's a possibility the pixels aren't square, so you have to check both X and Y resolution. From there, you need to pick your base. I like 96 because most "old" monitors use 96 DPI, and it's the same system WPF uses. Then, you need to make methods that convert. Here's a utility class that can be handy:
    Code:
    public class DpiScaler {
    
        public const float UnitsPerInch = 96;
    
        public float DpiX { get; private set; }
        public float DpiY { get; private set; }
    
        public DpiScaler() {
            using (var g = Graphics.CreateGraphics()) {
                DpiX = g.DpiX;
                DpiY = g.DpiY;
            }
        }
    
        // 96 units = 1 inch = DpiX pixels
        public int ToPixelsX(float units) {
            float inches = units / UnitsPerInch;
            return (int)Math.Round(DpiX * inches);
        }
    
        // 96 units = 1 inch = DpiY pixels
        public int ToPixelsY(float units) {
            float inches = units / UnitsPerInch;
            return (int)Math.Round(DpiY * inches);
        }
    }
    You don't have to convert font sizes, because Points are already 72 points per inch. A 12 point font should be the same size on any monitor. You can go out of your way to specify pixels, but that's trouble.

    So say you had a button in which you wanted to put a 12 point font. 12 points is 1/6", and if we add some padding let's say we end up with 1/3" worth of button. The width is by default 48 pixels, which is about 1/2" on a "standard" screen. Assuming our form keeps a scaler around:
    Code:
    float oneInch = _dpiScaler.UnitsPerInch;
    int buttonWidth = _dpiScaler.ToPixelsX(oneInch / 2);
    int buttonHeight = _dpiScaler.ToPixelsY(oneInch / 3);
    
    okButton.Size = new Size(buttonWidth, buttonHeight);
    In a perfect world, that'd just work. On a "normal" monitor, DpiX and DpiY are 96, so we end up with a size of (48, 32). On a Surface, if DpiX and DpiY are 225, we'll get (112, 68).

    Unfortunately, unless you jump through a lot of hoops to tell Windows your application is DPI aware and turn off ALL auto-scaling, Windows will try to help you and make a serious mess. I know how to turn off WinForms auto-scaling, but not how to tell Windows the application is DPI-aware.

    One thing not covered: if you use bitmap images in your app, they take special considerations too. Every image has a resolution of its own, and on the web it's typically 72 dpi. So an image that fills a nice 3-inch square on your monitor should be 216x216. If you're using PictureBox, generally setting properties like SizeMode is good enough. But stretching a 72 DPI image to its normal size on a high density monitor can make it look REALLY ugly. The alternative is it ends up barely 1 inch square. So generally, if you have to handle high density displays AND use pre-generated bitmapped graphics, it's best to generate high-resolution versions in addition to low-resolution versions, and choose the right ones on the fly.

    Another snag: if you want to support multiple monitors, Windows 10 is aware of the concept that not every monitor has the same DPI. As best as I can tell, that means you need to handle some API events that indicate when the form changes monitors, then re-query the DPI and redo all of the layout work.

    MacOS, iOS, and Android developers don't worry about it as much, those layout systems were designed to support multiple resolutions and display densities. Same with WPF, Silverlight, Win8 apps, and UWP, which all use 96-per-inch units instead of pixels. It's only Windows Forms that requires so much work for DPI scaling. I think you can see in that DPI isn't part of the Screen class that they didn't really expect it to be such a big problem, or that they expected people to move to a new framework before it was a major problem. It's not really new, I read about it in the Petzold Windows book way back in 1996. It's 'more work' and there weren't any monitors that made it a big deal back then, so it never got done.
    Last edited by Sitten Spynne; Apr 21st, 2017 at 03:31 PM.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  6. #6

    Thread Starter
    PowerPoster MMock's Avatar
    Join Date
    Apr 2007
    Location
    Driving a 2018 Mustang GT down Route 8
    Posts
    4,475

    Re: Building winforms application on laptop, for large screen monitors

    I can perhaps solve this with docking controls. This is the requirement: "For the main screen, when they resize the form, the main schedule grid should get bigger, The other items can remain a fixed size." What that means is we really only care about one of the grids. We want the columns to grow when the form grows and shrink when it shrinks. This grid is on a tabpage in a tabcontrol. To the left of the grid in the tab are groups with textboxes, buttons, etc. I think I am having a problem with the form being already built and the large hierarchy of controls on it. I have a simple working example, but it's hard to adapt it. I've put a dockmanager to the right of the tab. But when I size the form, it is the tab that is growing and shrinking, not the grid. Do I need docking controls within docking controls?
    There are 10 kinds of people in this world. Those who understand binary, and those who don't.

  7. #7
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Building winforms application on laptop, for large screen monitors

    What you're describing sounds like experimentation with TableLayoutPanel could work. I got a little lost in there but it sounds to me like you're going to have to do the tricky thing where you have to reparent the large heirarchy of controls into a new TableLayoutPanel.

    It's kind of hard to share designer-oriented code but I'm thinking it sounds like you have 2 columns and ??? rows. It sounds like to me you could approximate it with:

    • The TableLayoutPanel is the only child of the form, with Dock set to Fill.
    • There is one row per GroupBox. Let's say there are four of them for the sake of explanation.
      • The top three rows contain a GroupBox with Dock set to Fill. Each row's height should be set to AutoSize.
      • The bottom row contains a GroupBox with its Dock set to Top. Its height should be set to 100 "percent".
    • The Grid is technically in the top-right cell, or column 1 row 0. Dock is set to Fill. Here's a grid-specific thing: you'll find a RowSpan property in the property grid for it. Set it to 4. Now the grid fills every row.
    • Edit the column sizes.
      • You don't want the left columns to grow. They should either be AutoSize or set to an Absolute value you've calculated.
      • You want the right columns to grow. Set it to 100 "percent".

    Setting this up will be a pain in the butt, it's really easy to make rows/columns have 0 width/height. I suggest you make a few practice runs, and if you aren't using source control you DEFINITELY want to make a .zip of your project before proceeding. You're going to screw up a few times and the easiest way to go back is sometimes to just drag/drop the old files back. I cannot stress enough you should do some practice runs in a test project.

    You might want to make the form really big, then drag the TLP onto empty space, make it big enough to contain the stuff you want, then drag things ONE BY ONE into it. Only proceed to the next step if it looks like what you want.

    (A lot of times, I set up my table layouts in code, I find it less frustrating than trying to get the designer to do what I want.)
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  8. #8

    Thread Starter
    PowerPoster MMock's Avatar
    Join Date
    Apr 2007
    Location
    Driving a 2018 Mustang GT down Route 8
    Posts
    4,475

    Re: Building winforms application on laptop, for large screen monitors

    I am back, and I have to stick with this now until it's resolved. It is perhaps a Windows operating system problem. (And just know up front, I am going to say stupid things because I don't know anything about this.) The reason why I say it's an OS issue is my boss had me deploy my application, still in development but hopefully it will be released soon, on the laptop of one of our techs. He is running Windows 7 and his resolution is 1366x768. The main screen is cut off on the left. We opened one other form from there, it's narrower so its width fit, but it is longer so it got cut off at the bottom. So there's my problem. And the title of this thread is perhaps a misnomer. What the asana task in our system is called is "Sizing forms on large and small monitors". At least I have a controlled group of users because it's an internal company application.

    So Sitten, you've given me some things to digest and I will look at them very closely now. Can I ask, once I make some changes in Visual Studio using my desktop which is my laptop docked and two large monitors at my disposal, do I have to undock and run on my laptop with the 1366x768 settting to see what my user (we will call him Todd) is going to get?
    There are 10 kinds of people in this world. Those who understand binary, and those who don't.

  9. #9
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Building winforms application on laptop, for large screen monitors

    I would do everything I can to reproduce the user's environment as precisely as possible. That would include undocking, just to make sure the presence of other monitors doesn't cause a different behavior.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  10. #10

    Thread Starter
    PowerPoster MMock's Avatar
    Join Date
    Apr 2007
    Location
    Driving a 2018 Mustang GT down Route 8
    Posts
    4,475

    Re: Building winforms application on laptop, for large screen monitors

    Yup. I write some code then yank the laptop off its dock and run with the low resolution...

    I neglected to mention I am using DevExpress controls, which perhaps is going to make my life easier. They have something called a LayoutControl. So I have one docked left and one docked right. The form looks pretty good except I have a grid in a tab on the left LayoutControl, and the last column of the grid (the rightmost) is chopped off because I guess there isn't enough room to display it. Maybe I need to look at some of the grid properties because perhaps I am not letting the columns grow and shrink. But I guess I'm making progress.
    There are 10 kinds of people in this world. Those who understand binary, and those who don't.

  11. #11

    Thread Starter
    PowerPoster MMock's Avatar
    Join Date
    Apr 2007
    Location
    Driving a 2018 Mustang GT down Route 8
    Posts
    4,475

    Re: Building winforms application on laptop, for large screen monitors

    This has come together well. I will explain my solution in a bit, but I have a question which is kind of OT but here goes. I had a form called frmJobEntry which needed to size. I created a new form called frmJobEntry3 so I could create the sizing controls I needed, then put all the existing controls on top. I copied from the original file to the new file. A lot of event handlers aren't being called because I still need to hook them up. That is, I have an Exit button and cmdExit_Click() exists but I am not subscribing to it yet so nothing happens when I click Exit. There are a lot of these - button clicks, checkbox changes, dropdownlist selection changes and I want a quick way to find them all. Visually, I can see that whenever a method says 0 references I need to subscribe to it. But I want a quicker way of doing this than scrolling through an 8000 line of code file.

    I found this question on stackoverflow and implemented the solution, but oddly it is finding 0 references in other files but not the one I am looking for! All these modules are in the same project and I am running "Run Code Analysis on Solution". At this point, the hour I've wasted trying to learn a new tool could have been better spent on eyeballing the file. I might've been on line 3000 by now LOL!
    There are 10 kinds of people in this world. Those who understand binary, and those who don't.

  12. #12

    Thread Starter
    PowerPoster MMock's Avatar
    Join Date
    Apr 2007
    Location
    Driving a 2018 Mustang GT down Route 8
    Posts
    4,475

    Re: Building winforms application on laptop, for large screen monitors

    You might want to see my ruleset.
    Code:
    <?xml version="1.0" encoding="utf-8"?>
    <RuleSet Name="Zero References" Description=" " ToolsVersion="12.0">
      <Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
        <Rule Id="CA1811" Action="Warning" />
      </Rules>
    </RuleSet>
    There are 10 kinds of people in this world. Those who understand binary, and those who don't.

  13. #13
    Superbly Moderated NeedSomeAnswers's Avatar
    Join Date
    Jun 2002
    Location
    Manchester uk
    Posts
    2,660

    Re: Building winforms application on laptop, for large screen monitors

    One thing i have found myself doing in nearly all my desktop projects now is having a sub specifically to add all my handlers rather than have Visual Studio do it for me in the designer file.

    This gives me 2 main benefits,

    1, If i need to copy the form or upgrade all the controls on a form or something like that, i never have to reattach all the handlers.

    2, I have complete control as to when the handlers are attached, so they dont run when i am loading data which often causes problems or causes me to write crappy If statements in the handlers saying dont run on data load or some other event.
    Please Mark your Thread "Resolved", if the query is solved & Rate those who have helped you



  14. #14

    Thread Starter
    PowerPoster MMock's Avatar
    Join Date
    Apr 2007
    Location
    Driving a 2018 Mustang GT down Route 8
    Posts
    4,475

    Re: Building winforms application on laptop, for large screen monitors

    NeedSome - yes, I definitely agree! Wish I had had that foresight earlier, but better luck next time.
    There are 10 kinds of people in this world. Those who understand binary, and those who don't.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width