Skip to content

Commit 4bbc74d

Browse files
committed
Initial version
1 parent 1605cb8 commit 4bbc74d

File tree

65 files changed

+3646
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+3646
-0
lines changed

Diff for: .gitattributes

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
###############################################################################
2+
# Set default behavior to automatically normalize line endings.
3+
###############################################################################
4+
* text=auto
5+
6+
###############################################################################
7+
# Set default behavior for command prompt diff.
8+
#
9+
# This is need for earlier builds of msysgit that does not have it on by
10+
# default for csharp files.
11+
# Note: This is only used by command line
12+
###############################################################################
13+
#*.cs diff=csharp
14+
15+
###############################################################################
16+
# Set the merge driver for project and solution files
17+
#
18+
# Merging from the command prompt will add diff markers to the files if there
19+
# are conflicts (Merging from VS is not affected by the settings below, in VS
20+
# the diff markers are never inserted). Diff markers may cause the following
21+
# file extensions to fail to load in VS. An alternative would be to treat
22+
# these files as binary and thus will always conflict and require user
23+
# intervention with every merge. To do so, just uncomment the entries below
24+
###############################################################################
25+
#*.sln merge=binary
26+
#*.csproj merge=binary
27+
#*.vbproj merge=binary
28+
#*.vcxproj merge=binary
29+
#*.vcproj merge=binary
30+
#*.dbproj merge=binary
31+
#*.fsproj merge=binary
32+
#*.lsproj merge=binary
33+
#*.wixproj merge=binary
34+
#*.modelproj merge=binary
35+
#*.sqlproj merge=binary
36+
#*.wwaproj merge=binary
37+
38+
###############################################################################
39+
# behavior for image files
40+
#
41+
# image files are treated as binary by default.
42+
###############################################################################
43+
#*.jpg binary
44+
#*.png binary
45+
#*.gif binary
46+
47+
###############################################################################
48+
# diff behavior for common document formats
49+
#
50+
# Convert binary document formats to text before diffing them. This feature
51+
# is only available from the command line. Turn it on by uncommenting the
52+
# entries below.
53+
###############################################################################
54+
#*.doc diff=astextplain
55+
#*.DOC diff=astextplain
56+
#*.docx diff=astextplain
57+
#*.DOCX diff=astextplain
58+
#*.dot diff=astextplain
59+
#*.DOT diff=astextplain
60+
#*.pdf diff=astextplain
61+
#*.PDF diff=astextplain
62+
#*.rtf diff=astextplain
63+
#*.RTF diff=astextplain

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -396,3 +396,4 @@ FodyWeavers.xsd
396396

397397
# JetBrains Rider
398398
*.sln.iml
399+
/.idea/

Diff for: Directory.Build.props

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project>
2+
<!-- See https://aka.ms/dotnet/msbuild/customize for more details on customizing your build -->
3+
<PropertyGroup>
4+
5+
<LangVersion>latestMajor</LangVersion>
6+
<Nullable>enable</Nullable>
7+
8+
</PropertyGroup>
9+
</Project>

Diff for: FubarDev.LayoutEngine.sln

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.7.34031.279
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AC9E39BD-6122-4C73-8A66-A989193039B3}"
7+
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DE5E7D6F-9427-47EF-8D01-81A7AA31BCFB}"
9+
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{88ED1BE0-102C-4397-B238-659C33CB4DB8}"
11+
EndProject
12+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FubarDev.LayoutEngine", "src\FubarDev.LayoutEngine\FubarDev.LayoutEngine.csproj", "{E99A0E1F-9F40-470C-9D15-BB4F220812CA}"
13+
EndProject
14+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FubarDev.LayoutEngine.WinForms", "src\FubarDev.LayoutEngine.WinForms\FubarDev.LayoutEngine.WinForms.csproj", "{52C4E451-B319-42FE-99EF-A2F4755044C0}"
15+
EndProject
16+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FubarDev.LayoutEngine.Test", "tests\FubarDev.LayoutEngine.Test\FubarDev.LayoutEngine.Test.csproj", "{A0A439AF-7363-4199-8AEB-AFEB0259241E}"
17+
EndProject
18+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestLayoutEngine", "samples\TestLayoutEngine\TestLayoutEngine.csproj", "{58050EF8-5540-4A51-A7CF-8966A60C426C}"
19+
EndProject
20+
Global
21+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
22+
Debug|Any CPU = Debug|Any CPU
23+
Release|Any CPU = Release|Any CPU
24+
EndGlobalSection
25+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
26+
{E99A0E1F-9F40-470C-9D15-BB4F220812CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27+
{E99A0E1F-9F40-470C-9D15-BB4F220812CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
28+
{E99A0E1F-9F40-470C-9D15-BB4F220812CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
29+
{E99A0E1F-9F40-470C-9D15-BB4F220812CA}.Release|Any CPU.Build.0 = Release|Any CPU
30+
{52C4E451-B319-42FE-99EF-A2F4755044C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31+
{52C4E451-B319-42FE-99EF-A2F4755044C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
32+
{52C4E451-B319-42FE-99EF-A2F4755044C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
33+
{52C4E451-B319-42FE-99EF-A2F4755044C0}.Release|Any CPU.Build.0 = Release|Any CPU
34+
{A0A439AF-7363-4199-8AEB-AFEB0259241E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35+
{A0A439AF-7363-4199-8AEB-AFEB0259241E}.Debug|Any CPU.Build.0 = Debug|Any CPU
36+
{A0A439AF-7363-4199-8AEB-AFEB0259241E}.Release|Any CPU.ActiveCfg = Release|Any CPU
37+
{A0A439AF-7363-4199-8AEB-AFEB0259241E}.Release|Any CPU.Build.0 = Release|Any CPU
38+
{58050EF8-5540-4A51-A7CF-8966A60C426C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39+
{58050EF8-5540-4A51-A7CF-8966A60C426C}.Debug|Any CPU.Build.0 = Debug|Any CPU
40+
{58050EF8-5540-4A51-A7CF-8966A60C426C}.Release|Any CPU.ActiveCfg = Release|Any CPU
41+
{58050EF8-5540-4A51-A7CF-8966A60C426C}.Release|Any CPU.Build.0 = Release|Any CPU
42+
EndGlobalSection
43+
GlobalSection(SolutionProperties) = preSolution
44+
HideSolutionNode = FALSE
45+
EndGlobalSection
46+
GlobalSection(NestedProjects) = preSolution
47+
{E99A0E1F-9F40-470C-9D15-BB4F220812CA} = {AC9E39BD-6122-4C73-8A66-A989193039B3}
48+
{52C4E451-B319-42FE-99EF-A2F4755044C0} = {AC9E39BD-6122-4C73-8A66-A989193039B3}
49+
{A0A439AF-7363-4199-8AEB-AFEB0259241E} = {DE5E7D6F-9427-47EF-8D01-81A7AA31BCFB}
50+
{58050EF8-5540-4A51-A7CF-8966A60C426C} = {88ED1BE0-102C-4397-B238-659C33CB4DB8}
51+
EndGlobalSection
52+
GlobalSection(ExtensibilityGlobals) = postSolution
53+
SolutionGuid = {A483F1C7-EF2E-482E-8FE3-F049E629539D}
54+
EndGlobalSection
55+
EndGlobal

Diff for: FubarDev.LayoutEngine.sln.DotSettings

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BETWEEN_USING_GROUPS/@EntryValue">1</s:Int64>
3+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
4+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
5+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
6+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

Diff for: README.md

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# Layout engine for Windows Forms applications
2+
3+
This is a layout engine for WinForms applications, which took
4+
inspirations from the following sources:
5+
6+
- [WPF Grid](https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.grid)
7+
- [ETS Layout](https://www.codeproject.com/articles/116/layout-manager-for-dialogs-formviews-dialogbars-an)
8+
9+
# Example
10+
11+
This is the dialog we'd like to layout:
12+
13+
![Sample dialog](doc/images/sample-dialog.png)
14+
15+
## Requirements
16+
17+
- "pnlLogo" must always be at the left top position
18+
- "pnlModuleSelector" must fill the space between the logo and the "pnlDashboard"
19+
- "pnlMenu" and "pnlDialogView" must always stay together
20+
- "pnlMenu" and "pnlDialogView" must always be in the center of the remaining space
21+
22+
## Solution
23+
24+
We use panes to group together the various controls/panes.
25+
26+
- We always need to create a root pane with a horizontal layout
27+
- The root pane is split into two sub-panes
28+
- The pane for the space left to the dashboard
29+
- The pane for the dashboard
30+
- The pane for the space left to the dashboard is split into four elements
31+
- The pane for the "pnlLogo" and the "pnlModuleSelector" (pane 1)
32+
- The pane to fill the gap between the header and the next pane to keep the next pane centered
33+
- The pane which contains the panes 4, 5, the "pnlMenu" and the "pnlDialogView"
34+
- Another pane to fill the gap between the bottom and the previous pane to keep the previous pane centered
35+
36+
## Code for the layout definition
37+
38+
```c#
39+
_layoutRoot =
40+
(CreateRoot(this, Orientation.Horizontal)
41+
<< (Pane(Orientation.Vertical).Width(Factor(1))
42+
<< (Pane().HorizontalStackLayout(VerticalAlignment.Top)
43+
<< Item(pnlLogo)
44+
<< Item(pnlModuleSelector).Width(Factor(1)))
45+
<< Item().Height(Factor(1))
46+
<< (Pane().HorizontalStackLayout(VerticalAlignment.Center)
47+
<< Item().Width(Factor(1))
48+
<< Item(pnlMenu)
49+
<< Item(pnlDialogView)
50+
<< Item().Width(Factor(1)))
51+
<< Item().Height(Factor(1)))
52+
<< Item(pnlDashboard))
53+
.Build();
54+
```
55+
56+
The functions `CreateRoot`, `Pane`, and `Item` are static functions in the static
57+
`FubarDev.LayoutEngine.BuilderMethods` class. I'm using a the `using static` C#
58+
feature to simplify the function calls:
59+
60+
```c#
61+
using static FubarDev.LayoutEngine.BuilderMethods;
62+
using static FubarDev.LayoutEngine.AttachedProperties.AttachedSize;
63+
```
64+
65+
This makes all static functions in the `BuilderMethods` class directly available.
66+
67+
### Line-by-line explanation
68+
69+
1. Store the built root pane into the variable `_layoutRoot`
70+
2. The `CreateRoot` creates the root pane for the `Form` whose elements are aligned horizontally
71+
3. A sub-pane is created for the space left to the "pnlDashboard"
72+
- The child elements are stacked vertically
73+
- The element should consume the whole remaining space (see "*" notation of the WPF grid for grid rows/columns)
74+
4. A sub-pane is created for the header (pane 1)
75+
- The child elements are stacked horizontally
76+
- The default vertical alignment for the elements will be "top"
77+
- This causes the child elements to keep their height
78+
5. Add the "pnlLogo" as first child element of the header pane (pane 1)
79+
6. Add the "pnlModuleSelector" as the second child element which should use the remaining width of the header pane (pane 1)
80+
7. Adds a spacer pane (pane 2)
81+
8. Adds the pane containing the spacer panes 4 and 5, and the "pnlMenu" and "pnlDialogView"
82+
- All child elements should be centered vertically
83+
9. Adds the spacer pane (pane 4)
84+
- This pane, together with pane 5 ensures that the "pnlMenu" and "pnlDialogView" are centered horizontally
85+
- The `.Width(AttachedSize.Factor(1))` on this pane and pane 5 causes the space
86+
not used by "pnlMenu" and "pnlDialogView" to be split equally between the panes 4 and 5.
87+
10. Adds the "pnlMenu"
88+
11. Adds the "pnlDialogView"
89+
12. Adds the spacer pane (pane 5)
90+
13. Adds the spacer pane (pane 3)
91+
14. Adds the "pnlDashboard"
92+
93+
Using `Orientation.Horizontal` for a pane results in the child elements
94+
being stretched to the full height of the surrounding pane. This behavior
95+
can be changed by omitting the `Orientation.Horizontal` and using the
96+
method `.HorizontalStackLayout(VerticalAlignment.Top)` to provide a different
97+
default vertical alignment.
98+
99+
## Example dump
100+
101+
This dump contains the names of all elements for a layout, together
102+
with the calculated minimum size for the element.
103+
104+
```text
105+
root: {X=467,Y=415,Width=1232,Height=988}, {Width=1086, Height=762}
106+
nonDashboardArea: {X=3,Y=4,Width=875,Height=916}, {Width=753, Height=754}
107+
headerArea: {X=3,Y=4,Width=875,Height=170}, {Width=154, Height=170}
108+
pnlLogo: {X=6,Y=8,Width=142,Height=162}, {Width=142, Height=162}
109+
pnlModuleSelector: {X=154,Y=8,Width=721,Height=162}, {Width=0, Height=162}
110+
spacerTop: {X=3,Y=174,Width=875,Height=81}, {Width=0, Height=0}
111+
mainArea: {X=3,Y=255,Width=875,Height=584}, {Width=753, Height=584}
112+
spacerLeft: {X=3,Y=547,Width=61,Height=0}, {Width=0, Height=0}
113+
pnlMenu: {X=67,Y=259,Width=225,Height=576}, {Width=225, Height=576}
114+
pnlDialogView: {X=298,Y=259,Width=516,Height=576}, {Width=516, Height=576}
115+
spacerRight: {X=817,Y=547,Width=61,Height=0}, {Width=0, Height=0}
116+
spacerBottom: {X=3,Y=839,Width=875,Height=81}, {Width=0, Height=0}
117+
pnlDashboard: {X=881,Y=8,Width=321,Height=908}, {Width=321, Height=0}
118+
```
119+
120+
## Performing the layout
121+
122+
The layout won't be performed automatically, which is a design decision,
123+
not a technical decision.
124+
125+
To perform the layout, you must explicitly execute the following code:
126+
127+
```c#
128+
_layoutRoot.Layout();
129+
```
130+
131+
## Setting the minimum size of a form
132+
133+
The minimum size of the form can only be calculated if the form (and thus all
134+
its controls) are visible, which is the reason why you should put the following
135+
code into the `VisibleChanged` event handler of the form:
136+
137+
```c#
138+
var minSize = _layoutRoot.GetMinimumClientSize();
139+
var borderSize = Size - ClientSize;
140+
MinimumSize = minSize + borderSize;
141+
```
142+
143+
# Advanced features
144+
145+
## Overlaps
146+
147+
It's possible to make elements overlap each other.
148+
149+
### Example
150+
151+
```c#
152+
_layoutRoot =
153+
(CreateRoot(this, Orientation.Horizontal)
154+
<< (Pane(Orientation.Vertical).Width(Factor(1))
155+
<< (Pane().HorizontalStackLayout(VerticalAlignment.Top)
156+
<< Item(pnlLogo)
157+
<< Item(pnlModuleSelector).Width(Factor(1)))
158+
<< Pane().Height(Factor(1)).Identifier("a"))
159+
<< Item(pnlDashboard))
160+
.AddOverlap(
161+
"a",
162+
Pane(Orientation.Vertical)
163+
<< Item().Height(Factor(1))
164+
<< (Pane().HorizontalStackLayout(VerticalAlignment.Center)
165+
<< Item().Width(Factor(1))
166+
<< Item(pnlMenu)
167+
<< Item(pnlDialogView)
168+
<< Item().Width(Factor(1)))
169+
<< Item().Height(Factor(1)))
170+
.Build();
171+
```
172+
173+
As you might've noticed, there's now a new function to `Identifier`.
174+
This adds an application-global identifier to the given layout element.
175+
Items that overlap the items with the given `Identifier` may now
176+
be overlapped with the other controls by calling the `AddOverlap`
177+
function, which takes the identifier of the layout element to
178+
be overlapped and the layout that will overlap the target
179+
layout element.
180+
181+
You may specify more that one "overlap" for a given layout element.
182+
183+
The idea behind this is, that you may have two or more controls
184+
that should take the same space in the form, but only one should be visible
185+
at all times.

Diff for: doc/images/sample-dialog.png

38.9 KB
Loading

Diff for: samples/TestLayoutEngine/DrawingExtensions.cs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Drawing;
2+
using System.Windows.Forms;
3+
4+
namespace TestLayoutEngine;
5+
6+
public static class DrawingExtensions
7+
{
8+
public static Point Add(this Point point, Point offset)
9+
=> new(point.X + offset.X, point.Y + offset.Y);
10+
11+
public static Rectangle Add(this Rectangle rectangle, Point offset)
12+
=> rectangle with {X = rectangle.X + offset.X, Y = rectangle.Y + offset.Y};
13+
14+
public static Rectangle Add(this Rectangle rectangle, Padding margin)
15+
=> new(rectangle.X - margin.Left, rectangle.Y - margin.Top,
16+
rectangle.Right + margin.Right, rectangle.Bottom + margin.Bottom);
17+
18+
public static Rectangle Subtract(this Rectangle rectangle, Padding padding)
19+
=> new(rectangle.X + padding.Left, rectangle.Y + padding.Top,
20+
rectangle.Right - padding.Right, rectangle.Bottom - padding.Bottom);
21+
}

Diff for: samples/TestLayoutEngine/LayoutPanel.Designer.cs

+42
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)