Michael Angstadt's Homepage


How this page works

More blog entries >>

06/11/2020 2:26 pm

05/27/2020 4:24 pm
Over the past several months, I have been playing a factory-building game called Satisfactory. The game involves a lot of number crunching for calculating the input and output rates of the machines. You also have to think about how to optimally arrange the machines and conveyor belts in physical space on the factory floor. Because of this, I've found it to be much easier to design my factories outside of the game so that my in-game time can be spent actually building them!

I had decided to use Microsoft Excel to create these factory designs since I was already familiar with Microsoft Office's shape drawing and image editing tools. For the past ten months, I've been sharing many of my designs with the community via Reddit and Google Drive (I've built up a bit of a reputation for being the "floor plan guy"). I've become quite adept at using Excel for this purpose, so I thought I would share some of the things I've learned.

1. Snap to Grid

The "Snap to Grid" setting is crucial for helping to keep your shapes aligned with the worksheet grid lines.

To toggle it, go to: [ Page Layout tab > Arrange group > Align button > Snap to Grid]

2. Select Objects

The Select Objects tool allows you to select multiple objects by clicking and dragging to draw a selection box. Any object that is fully inside of the selection box will be selected. Objects that are only partially inside the box will not be selected. While this tool is activated, only objects can be selected--the cells inside of the actual spreadsheet cannot be selected.

To toggle it, go to: [ Home tab > Editing group > Find and Select button > Select Objects ]

3. Quick Access Toolbar

Located in the upper-left corner of the window, the Quick Access Toolbar serves as a holding place for your personal collection of favorite commands. Any button that's on the ribbon can be added to this toolbar. I've added buttons for "Snap to Grid" and "Select Objects" because I use these commands so much.

To customize the Quick Access Toolbar, click on the arrow on the right side of the toolbar and click "More Commands".

4. Grouping

Sometimes, you need to create multiple objects to achieve a specific goal. For example, to create the "clipboard" graphic pictured below, I needed:
  • An image for the "board" part of the clipboard
  • An image for the "clip" part of the clipboard
  • An image for the pencil
  • A textbox for the paper

To help manage complex collections of objects like this, you can group them together. Grouping allows multiple objects to be treated as a single object, allowing you to move and resize them as a whole. Even though they are grouped, you can still manipulate each individual "sub" object by clicking on them to select them.

To group a collection of objects, select them all by holding down the Shift key and clicking on each object (or by using the Select Objects tool, described above). Then, go to: [ Page Layout tab > Arrange group > Group button > Group ]. They can be ungrouped by selecting "Ungroup" from the same menu.

5. Nudging objects with the arrow keys

It can be difficult to precisely position an object using just the mouse. The arrow keys on the keyboard can be used to "nudge" an object in a particular direction. Tapping an arrow key will move the object one pixel at a time. Or, if Snap to Grid is enabled, the object will move one column/row at a time.

6. Zoom level issues

My experience has been that the sizes and positions of objects change slightly when you adjust the zoom level. To keep everything precise, I find it helpful to only move and resize objects at a specific zoom level. The zoom level that I work at is 100%.

It's also useful to note that the way Excel renders text can change when you adjust the zoom level. For example, you may have a textbox that wraps in a certain way at one zoom level, and then wraps differently at another zoom level. For example, the images below show the same textbox at two different zoom levels. Notice how the word wrapping changes starting with line 3.

To adjust the zoom level, use the slider in the bottom-right corner of the window or roll the mouse scroll wheel while holding Ctrl.

7. Paste as image

When an object, or group of objects, is copied to the clipboard, Excel will automatically generate a paste-able image when you paste into any image editing program, such as Photoshop or Paint. It even takes transparency into account! For example, if you copy a textbox that has a drop shadow, the drop shadow will render as semi-transparent pixels. This is useful for generating images out of the objects you've created in Excel (as I have done with my factory floor plans).

8. Image quality settings

By default, when you save an Excel file, Excel compresses all images to reduce the size of the file. For example, if you've inserted an image that's 800 pixels wide, and then resized it to be 400 pixels wide, Excel will down-sample the image to 400 pixels. The downside to this is that, if you later decide to make the image larger, it will look distorted because you've lost the original, 800 pixel version of the image.

To force Excel to preserve the original quality of all images, follow the steps below. These settings are applied only to the spreadsheet file you currently have open (they are not global). And if your spreadsheet already has images in it, you'll have to re-insert them to restore their original sizes.
  1. Go to [ File > Options > Advanced ].
  2. Scroll down to the "Image Size and Quality" section.
  3. Click the "Do not compress images in file" checkbox.
  4. Change "Default resolution" to "High fidelity".


I hope that my list of tips help you to "excel" at Excel! Happy spreadsheeting!
04/12/2020 10:01 pm
I maintain a handful of client-side Java programs that use Java's older GUI API called Swing. One thing that I have found awkward about Swing is the way dialog boxes are handled.

Swing provides a class called JOptionPane for creating simple dialog boxes. It alleviates the need to manually code your own, which saves a lot of time. All you have to do it make a single method call and your dialog appears.

While the class works just fine, some of its methods have up to eight parameters. When a method has more than three or four parameters, it makes it hard for someone who is not already familiar with the API to tell what the code does at a glance.

For example, the code below has a total of eight parameters. Unless you are already familiar with this method's signature, you probably don't have a clue as to what half of these parameters do.
int choice = JOptionPane.showOptionDialog(
    "The password you have chosen may not be secure.\n\n" +
      "Please keep the following guidelines in mind:\n\n" +
      "1. Eight or more characters long.\n" +
      "2. Is not a word from the dictionary." +
      "3. Completely different from all your other passwords.\n\n" +
      "Would you like to create a better password or continue?",
    "Security warning",
    new Object[]{"Pick a new password", "Continue"},
    "Pick a new password"
One thing you could do to make the code more readable is assign each parameter to a variable with a descriptive name. This allows a programmer who is not familiar with the method to see at a glance what each parameter is for.
Component parentWindow = this;
String text = 
    "The password you have chosen may not be secure.\n\n" +
    "Please keep the following guidelines in mind:\n\n" +
    "1. Eight or more characters long.\n" +
    "2. Is not a word from the dictionary." +
    "3. Completely different from all your other passwords.\n\n" +
    "Would you like to create a better password or continue?";
String title = "Security warning";
int buttons = JOptionPane.YES_NO_OPTION;
int messageType = JOptionPane.WARNING_MESSAGE
Icon icon = null; 
Object[] buttonLabels = new Object[]{"Pick a new password", "Continue"}
Object defaultButton = "Pick a new password";

int choice = JOptionPane.showOptionDialog(parentWindow, text, title, buttons, messageType, icon, buttonLabels, defaultButton);
However, there are still a some problems with this:
  1. Variable name conflicts: The variable names could be confused with other variable names in the same scope. For instance, the name "text" is fairly generic, so it's not unlikely that another variable a dozen or so lines down could have the same name.
  2. Parameters are ordered: It is very easy for the programmer to get the parameter order wrong without the compiler noticing. For example, if the "buttons" and "messageType" variables were swapped, the compiler wouldn't notice. The programmer likely wouldn't notice either because they are next to each other in the method signature.
  3. Null parameters: One of the parameters is null. The code would be more concise if we could just leave it out, but the JOptionPane class does not provide an appropriate method signature for that.
  4. Misleading code formatting (1): The way the message text string is formatted in the code makes it look like each line will end with a single line break. However, some lines end in two line breaks.
  5. Misleading code formatting (2): Each line of the message appears on its own line in the code. This makes it much easier to read! However, you may not have noticed that the fourth line is missing a line break. Because of the way the code is formatted, an error like this can easily be overlooked.
  6. Neglecting native system settings: The code may not be using the native system's preferred line break sequence. Appending "System.lineSeparator()" (or even a concisely-named "newline" variable) onto each line would be technically correct, but it would make the code less consice.
  7. Duplicate data: One of the button labels is duplicated.
  8. Ambiguous types: Defining button labels as Objects will lead to problems only detectable at runtime if an Object that JOptionPane does not accept is passed into the method (also, it's just weird!).
To resolve these issues, I designed a class that acts as a fluent wrapper around JOptionPane. Using this wrapper class, the above code would look like this:
int choice = DialogBuilder.warning()
    .title("Security warning")
        "The password you have chosen may not be secure.",
        "Please keep the following guidelines in mind:",
        "1. Eight or more characters long.",
        "2. Is not a word from the dictionary.",
        "3. Completely different from all your other passwords.",
        "Would you like to create a better password or continue?")
    .buttons(JOptionPane.YES_NO_OPTION, "*Pick a new password", "Continue")
The above problems are addressed as follows:
  1. Variable name conflicts: No variables are needed because the method name provides context. 
  2. Parameters are ordered: Each parameter is replaced by a method call. Methods can be called in any order.
  3. Null parameters: The class internally provides a default value for every optional parameter. The method call for this null parameter can be left out, making the code more concise.
  4. Misleading code formatting (1): The "text()" method takes a single vararg parameter, where each parameter is a line of text. If a line ends in two line breaks, an empty string is provided, making it clearer that an empty line will appear on the dialog box.
  5. Misleading code formatting (2): Line breaks are automatically added by the "text()" method.
  6. Neglecting native system settings: The "text()" method uses the native system's preferred line break sequence.
  7. Duplicate data: The default button is identified by an asterisk character, which is removed when the button label is passed into the JOptionPane class. This is not as obvious as I would like it to be, as it requires detailed reading of the Javadocs, but it is very concise.
  8. Ambiguous types: The button labels are defined as Strings in the "buttons()" method, not Objects. This means the compiler can prevent invalid objects from being passed in. 
The full class can be viewed here: https://github.com/mangstadt/emc-shopkeeper/blob/master/src/main/java/emcshop/gui/DialogBuilder.java
03/26/2020 11:14 am

CompTIA is an organization that administers certification exams for IT professionals. I took and passed their entry-level exam, CompTIA A+, three years ago. The exam tests your knowledge of broad IT-related topics, such as internal hardware, cabling, troubleshooting, operating systems, and much much (MUCH) more.

I wrote a handful of blog posts about some of these topics while I was studying for the exam if you want to get a feel for what they test you on. You can also get the exam objects from their website (the form asks your for your email, but you don't have to give a real one).

Having to pay over $400 to take a test (actually, it's two separate tests) that I might fail was stressful to me, so I'm glad that I passed it! The exam expires after three years, so since I took it three years ago, I needed to renew it. There are several ways they allow you to renew your certification. I chose to enroll in their CertMaster CE training course, which renews your certification instantly upon completion. It's all done online and costs $129.

The way it works is you read through the content it gives you, which is organized based on the exam objectives of the exam you are renewing. At the end of each section, it gives you a 20-30 question multiple choice quiz. When you complete the quiz, it shows you which questions you got wrong, but doesn't tell you the right answer. You can "reset the assessment", which resets the quiz and lets you take it again.

You can give it a date for when you want to complete the training by, and it will recommend to you how much content ("knowledge points") you have to study each day in order to finish by that date. As you progress through each section, you earn knowledge points. It tells you how many knowledge points are in each section so you can get a feel for how long a section is.

It also has various "achievements" that you can earn to help motivate you. For example, there's one for getting 100% on a quiz and one for getting twenty consecutive questions correct. My left-brain found some of them to be a little patronizing (for example, resetting a quiz gives you an achievement), but it was nice to have that extra bit of encouragement.

I found the user interface of this web app to be very clean and intuitive. It is also very responsive, which leads me to believe that it's heavily JavaScript-based and doing a lot of caching in the background. For example, going back and forth between the pages in a lesson is instantaneous, as is flipping through the questions in a quiz.

I also loved the graphics they provided for showing what the different cable connectors look like (VGA, DisplayPort, USB, etc). They all have a consistent aesthetic to them, which made the learning process that much easier. I would post one here as an example, but I don't want to violate any copyrights.

In order to renew your certification, you have to get a 100% on each quiz. Once you do that, your certification is automatically renewed. You literally don't have to do anything else. I got an email no more than a day or two after I completed all the quizzes saying that my certification was renewed. My new expiration date was based upon my current expiration date, not the date I completed the CertMaster training. I appreciate that because I completed the training several months early!
03/24/2020 10:12 am
A few months ago, I wrote a blog post about a problem related to the date modified timestamp on a database file not updating immediately after the file was modified. This was causing a problem with an application I wrote that relied on this timestamp to update the data it displayed on the screen to the end user. It would take around ten seconds for the timestamp to update, which meant that the user had to wait that amount of time before the most recent information was displayed to them.

While I did discover a clue as to why this was happening to some files and not others, I wasn't able to fix the core issue itself--the delayed updating of the date modified timestamp. However, I implemented a workaround that not only solved the problem of the end user getting delayed updates, but also improved the integrity of the file (an Access database file).

The workaround involved creating a lock file whenever a user writes to the database. When the user is done writing to the database, the application deletes the lock file. The application monitors for this file deletion event and refreshes itself whenever a deletion is detected. There are no delays in the file system reporting when a file is deleted, which means the user's screen updates instantaneously whenever the database is updated.

I also use the lock file to improve the integrity of the database file. If the user tries to write to the database, but the application detects the presence of the lock file, it means another user is currently writing to the database. If this happens, the application waits until the lock file is deleted before creating its own lock file and executing its own writes. This prevents multiple users from writing to the database simultaneously and potentially corrupting the database.

There is a second kind of lock file which the application also makes use of. If the file is opened in Microsoft Access, Access creates its own lock file. My application monitors for the presence of this lock file. If the user tries to write to the database while the Access lock file exists, the application cancels the write operation and displays an error message. The error message informs the user that the database is open in Access and that it cannot write to the database while Access is open. This also helps to prevent file corruption.

Similarly to the lock file that the application creates when the user writes to the database, the application uses the Access lock file to detect changes to the database. When the Access lock file is deleted, it not only means that Access has been closed, but that changes could have been made to the database while it was open. So, when this lock file is deleted, the application refreshes itself and updates the on-screen information for the end user.

This workaround has been in production for 1-2 months and has been working quite well.
More blog entries >>

How this page works

Last Updated: 1/3/2012

My blog is actually hosted on blogger.com. The way I'm able to display my blog posts here is by parsing the blog's RSS feed. RSS feeds are used by blogs to help alert their avid readers whenever a new post is created. They are just XML files that contain data on the most recent blog posts. They include things like the title and publish date of each post, as well as the actual blog post text. I can use most of the data from my RSS feed without any trouble, but there are a few things I need to tweak in order to display everything properly.

View the source

Fixing the code samples

One tweak is fixing the code samples I often include in my posts. Blogger replaces all newlines in the blog post with <br /> tags. This is a problem because, due to the syntax highlighting library I use, the <br /> tags themselves show up in the code samples. So, I need to replace all of these tags with newline characters. However, I can't just replace all <br /> tags in the entire blog post because I only want to replace the tags that are within code samples. This means that I have to use something a little more complex than a simple search-and-replace operation:

$content = //the blog post
$contentFixed = preg_replace_callback('~(<pre\\s+class="brush:.*?">)(.*?)(</pre>)~', function($matches){
	$code = $matches[2];
	$code = str_replace('<br />', "\n", $code);
	return $matches[1] . $code . $matches[3];
}, $content);

Here, I'm using the preg_replace_callback PHP function, which will execute a function that I define every time the regular expression finds a match in the subject string. I know that each code sample is wrapped in a <pre> tag and that the tag has a class attribute whose value starts with "brush:", so I use that information to find the code samples. Then, for each match the regular expression finds, it calls my custom function, where I have it replace the <br /> tags with newlines.

Fixing the dates

Because the publish dates of each blog post in the RSS feed are relative to the UTC timezone, I also have to make sure to apply my local timezone to each date. Otherwise, the dates will not be displayed correctly (like saying that I made a post at 2am in the morning).

$dateFromRss = 'Tue, 20 Dec 2011 02:30:00 +0000';
$dateFixed = new DateTime($dateFromRss);
$dateFixed->setTimezone(new DateTimeZone('America/New_York'));

Adding Highslide support to images

One extra feature that I included is adding Highslide support to each image (Highslide is a "lightbox" library which lets you view images in special popup windows). To do this, I load the blog post into a DOM, use XPath to query for all links that have images inside of them, and then add the appropriate attributes to the link tag.

$content = //the blog post

//XML doesn't like "&nbsp;", so replace it with the proper XML equivalent
//see: http://techtrouts.com/webkit-entity-nbsp-not-defined-convert-html-entities-to-xml/
$content = str_replace("&nbsp;", "&#160;", $content);

//load the text into a DOM
//add a root tag incase there isn't one
$xml = simplexml_load_string('<div>' . $content . '</div>');

//if there's a problem loading the XML, skip the highslide stuff
if ($xml !== false){
	//get all links that contain an image
	$links = $xml->xpath('//a[img]');
	//add the highslide stuff to each link
	foreach ($links as $link){
		$link->addAttribute('class', 'highslide');
		$link->addAttribute('onclick', 'return hs.expand(this)');

	//marshal XML to a string
	$content = $xml->asXML();
	//remove the XML declaration at the top
	$content = preg_replace('~^<\\?xml.*?\\?>~', '', $content);
	//trim whitespace
	$content = trim($content);
	//remove the root tag that we added
	$content = preg_replace('~(^<div>)|(</div>$)~', '', $content);

As you can see, the blog post text has to be awkwardly manipulated in order to be read into a DOM and written back out as a string. That's why I have a lot of comments here--when I have to revisit this code in 6 months, I won't be totally confused.

Caching the RSS file

One last thing to mention is that I cache the RSS file so that my website doesn't have to contact Blogger every time someone loads this page. When the cached file gets to be more than an hour old, a fresh copy of the file is downloaded from Blogger.

Back to top