After getting the window working, I wanted some interaction. I looked at "ProcessKey()" to get started. I simply uncommented it in the "OnWindowKeyDown" method and then followed the code until there were no build errors.
It's a very simple method as OOP recommends (I put in my own comments):
private void ProcessKey(char c)
{
if (EraseDisplay) //EraseDisplay is a property
{
Display = string.Empty; //Display is a property
EraseDisplay = false;
}
AddToDisplay(c);
}
This method first checks if we want to erase the display. (As commented, EraseDisplay and Display are properties of this class which I will look at next.) It then clears the Display property if need be and then calls a method to add the newly pressed character to the display.
Properties are a really cool feature of C#. This tutorial by C# Station is a great introduction to how they can be used. Basically, they take away all the hassle of using getter/setter methods while still allowing you to customize how getting/setting is performed. The EraseDisplay and Display properties are as simple as you can get -- and they could easily be removed to use the naked variables "_erasediplay" [sic] and "_display" -- but, by structuring the code so that those variables be handled through properties allows for expansion such as validation or calling subroutines, etc.
Lastly, "AddToDisplay" is called. I think the programmer for this one wanted to make an example of nesting conditionals because it seems unnecessary (and I would have done it differently). Anyway, it starts by checking if a decimal point is already displayed. I've seen something like "IndexOf" in Python. It simply returns the index of the position in the string of the character you're looking for. String indexes start at 0 so if it returns a number greater than or equal to 0 then it does appear in the string and the method returns without changing the display (what number system uses more than one decimal point?) otherwise, it inserts the decimal point at the end. If the user presses a number the next conditional gets invoked which simply inserts the number at the end. If the user presses backspace, the final conditional is invoked which removes a single character from the end or sets the display to empty if there are no more characters to remove. Finally, and in any situation except the forced return, there is a call to "UpdateDisplay".
I'm definitely getting a better understanding of OOP principles. Each method above is focused on a single task and, if it needs to do something tangential to its core function, it calls upon another method to do that job. "ProcessKey" only exists to get the display ready before sending the character onto "AddToDisplay" which which does the work of altering the state of the display before calling "UpdateDisplay" which actually renders the new state.
The "UpdateDisplay" method warrants a closer look because it is so simple yet does so much (and there's a lot of interesting stuff to talk about):
private void UpdateDisplay()
{
if (Display == String.Empty) //String.Empty vs Display.Length == 0
DisplayBox.Text = "0"; //DisplayBox is of type MyTextBox initialized in the constructor
else
DisplayBox.Text = Display;
}
I say this method is simple because all it does is check if the display is empty and sets the text of "DisplayBox" (what the user sees) to 0 or to whatever value is in Display. I say it does so much because it makes use of a conditional, an interesting compare, and introduces a GUI element that it must alter.
Back when I was working on the Alarm Clock Sample, I came across a website that compared String.Empty with Foo.Length tests. I found another article which explains why the length test would work so much faster. I would like someone to explain why it is ever useful because I intend to never use it in my own code from now on.
Now for the GUI. I first noticed "DisplayBox" is an instance of the class "MyTextBox" which is defined in the file mytextbox.cs. It inherits from "System.Windows.Controls.TextBox" and only overrides the "OnPreviewGotKeyboardFocus" event handler method and is being used exactly as recommended: mark it as handled and then invoke the base class implementation to keep the train rolling. Second, I saw that the "DisplayBox" instance is added to the layout by use of the Grid. The grid is setup with XAML so I stuck it in my "InitializeThis" method:
//<DockPanel Name="MyPanel">
MyPanel = new DockPanel();
//
MyGrid = new Grid();
MyGrid.Name = "MyGrid";
MyGrid.Background = Brushes.Wheat;
MyGrid.ShowGridLines = false;
//
MyPanel.Children.Add(MyGrid);
//
this.Content = MyPanel;
I declared each instance variable outside the methods and marked them as "static" because they are logically contained within the entire and every "window1" object. The rest of the code is a direct translation from XAML based on what I came to understand previously so I won't explain it further. However, I will explain my understanding of how "Grid" works. Again, I have to thank my Python experience for this. We tell the "DockPanel" that a Grid will manage the position and geometry of specified GUI objects (widgets). The Grid in .NET is much more sophisticated than TkInter in that you can specify many aspects of the Grid's display rather than simply indicate how it will manage geometry, and, you must specify which objects will be managed by the Grid. The content of the window is then set as the "DockPanel" so it knows what to render for viewing.
I will implement the button animations next to flesh out the GUI, play around with animations again, and get ready for implementing operations.
0 comments:
Post a Comment