Planet Trajectories
What is the purpose of the application?
The program calculates and draws planet trajectories (their relative distance to the selected "center" planet). The selected planets and settings can be saved into a file and then loaded from it. The program also supports exporting the image in .png or .bmp format.
Why planet trajectories?
During astronomy lessons in physics, we had to draw these trajectories manually (it took several hours). As my friend suggested to me, I started working on a program to ease the work. I came up with the position finding algorithm already during the lessons. It's actually not so complicated, but I had no idea that the application with all the stuff around (application design, my wished GUI, correct rendering, program flexibility, saving into file etc.) will grow into my largest project so far and will take dozens of hours to make it.
Note: For simplicity, any object orbiting another object is refered as the planet.
What have I learned?
I'd like to share some of my experiences I've gained during the development, and I hope they'll help solve problems similar to those I've encountered during the development.
Points connecting curve
Problem
How to connect planet trajectory points with a smooth curve? I didn't want to connect points with straight lines - it wouldn't look good, and for any slightly smoother curve it'd be necessary to generate many points. So what to do?
Solution
I realized that drawing such curve using Bezier looks rather complicated.
Then I found that the Windows Forms libraries already provide this feature,
specifically, the Graphics.DrawCurve(Pen pen, Point[] points);
method. So I decided to port the application from WPF to WinForms.
Scrollbar controls width adjustment
Problem
We have a FlowLayoutPanel
with
FlowDirection = TopDown
. We want a ScrollBar to appear when there
isn't enough space for the controls, so we set the AutoScroll
to
True
and the WrapContents
to False
. The
problem is that the ScrollBar appears as expected, but it takes some space in
the panel. Therefore, the controls don't fit within the panel's width, and the
horizontal ScrollBar also appears. This isn't the desired behavior. It'd be
better if the controls shrinked a bit horizontally to make some space for the
ScrollBar. But I haven't figured out how to achieve it without using my own code
in the background, but then I came up with another solution.
Solution
The solution is to use a classic panel, instead of the FlowLayoutPanel. The
panel's AutoScroll
is set to True
, and the controls
inside set their Dock
to Top
. Simple, right? If the
controls don't fit in, the vertical ScrollBar appears and the controls shrink a
bit to make some space for it.
Hiding DropDown items
Problem
The planets have the Parent
property, which specifies which
planet the planet is orbiting. At first, I was selecting the planet in a
ComboBox (DropDownStyle = DropDownList
), which was binded to the
planets list. The problem was that I couldn't set the planet itself or any of
its "children" as the parent, because these mustn't appear in the ComboBox. The
question is how to hide them inside the ComboBox. The first attempts were using
the Binding.Format
event, but without any success. The planets
would be either removed from the source BindingList (which is what we, of
course, don't want), or the binding wouldn't work because we'd copy the items
into a new collection. Another option I found was to hide the unwanted items
inside the ComboBox, but that had several issues. First, the ComboBox itself
would have to be rewritten to hide the unwanted items. This includes creating a
custom control and implementing its rendering and positioning, which might
create new problems. Second, users are always capable of finding a way to break
something. For example, if we used keyboard keys to control the form, we could
also use them to select the hidden items (which are still present, just not
visible). Third, it'd be necessary to modify the program to somehow update the
visibility information, which would interfere with the existing code and make it
very messy.
I've been looking for any working solution for a long time, but I haven't found anything functional. In the end, I've decided to try to find different way to select the parent planet. I came up with a dialog containing the ComboBox, which would appear after clicking a button. The ComboBox will then display the planets that can be used as the "parent". Then I realized something - there was no need to create a new dialog which contains only the original ComboBox.
Solution
The solution is to update the ComboBox in the Enter
event that
is triggered whenever the respective control becomes the active control of the
form. Here we simply get the valid items (only a single line of code when using
LINQ) and display them to the user. DropDown is updated whenever the user wants
to select a new item, using either the mouse or the keyboard.
Appendix: I forgot something anyway. When the user hoveres over the ComboBox
and scrolls, the selected item can be changed without triggering the Enter
event. You can see the final solution it the source code of the project
(MouseEnter
and MouseWheel
events).
DropDown empty item
Problem
When a planet doesn't orbit another planet (such as the Sun), its
Parent
property is set to null
. So it'd be a good idea
to add an empty item into the ComboBox to represent the null
value.
But we can't add the null
value into a collection or DropDown
because all empty places in the list are already null and nothing would
change.
Solution
The solution is to add an item with the "<None>" text in the method
that filters the appropriate planets (it can also be an empty string). When
casting the selected item in the SelectedValueChanged
event back to
its original type (in this case, the Planet
), instead of
(Planet) ComboBox.SelectedItem
, it's necessary to write
ComboBox.SelectedItem as Planet
. So casting a string to the planet
won't throw us an exception, but only make the value null
- just as
we want.
Automatic scale, centering and zooming
Problem
The distances between planets can be very different, and with the same scale we can sometimes see a single planet across the whole screen, and sometimes the entire solar system is just a dot. It'd be good to automatically adjust the scale.
Solution
Winforms will greatly ease this work with its built-in features. First we
translate the [0; 0] point to the center:
g.TranslateTransform(.VisibleClipBounds.Width / 2, g.VisibleClipBounds.Height / 2)
.
To change the scale, we simply pass the scale in both axes to the
Graphics.ScaleTransform(float sx, float sy)
method. Each rendered
point is then automatically translated. Now we have to find the proper scale.
This can be obtained as follows:
zoom * canvas size / rendered image max size
.
Picture fonts
Problem
Originally, I used image fonts such as Wingdings3 for the icons. However, I learned that on some devices the font isn't installed, and empty rectangles are displayed instead.
Solution
Although fonts can be imported in a slightly more complicated way using the system libraries, there still might be problems with copyrights. Making (even prettier ) icons in Inkscape and setting them as the background image of the controls was a matter of minutes, while having less work and wondering about nonsense copyright issues.
Files opening from the application
Problem
It'd be terrific if the application supported files opening function using the "Open with..." option or by dragging the file to the application icon.
Solution
The solution is very simple. I didn't need any help from the Internet, and it
was exactly as I thought. In the Program.cs
file, add the
string[] args
argument to the static void Main()
method (as in a console application). This will get the arguments with which the
application was launched (in this case, the array will contain individual file
paths).
Conclusion
I'd like to continue developing the application, but I don't know whether I'll have time for it. With all the guesswork, I’ve spent slightly less than 100 hours developing it, so I’ll be happy for your support (even a nice comment that the app works well ). Future releases may come up with, for example, a ProgressBar that indicates the rendering status.
Even though the application didn't fulfill its original purpose of saving work, I'm still very glad that I made it. The most precious thing about this is the experience I've gained.
As always, I'm looking forward to any comments, bug reports, and discussions about other possible solutions that come to your mind.
Gallery
Download
By downloading the following file, you agree to the license terms
Downloaded 9x (458.72 kB)
Application includes source codes in language C# .NET