Log window from scratch
In the previous post we went through some interesting functionalities for our log window but we were missing the, arguably, most important one.
Should we start working on it? Oh yes, and you’ll love it.
This wouldn’t be a proper log window if we didn’t have log levels (debug, warning, error) and colors for them!
Again, we’ll let the host program decide which log levels it uses, their names and even the colors. And yes, you’ve guessed right, we’ll do that in the
But before we start, what’s the relationship between different log levels? How about using the common one: each level represents an integer (higher means more severity). Like in this example:
Imagine we had three log levels: debug, warning and error. We could use debug to log data or flow, warning would let us know of non-blocking incompatibilities or to diagnose future issues and error would flag stuff that must be fixed although we managed to continue executing the program. If we set our current log level to warning, we would also see error ones because their severity is higher.
Oh, and for it to work we must remember to update our
LogEntry messages to have a log level as well!
Configuration from the host
We’ve decided each log level is modelled as a name and a color. The severity can be implicit based on the configuration: the first ones are less important. What about adding this to
We could’ve created a new class or struct as the
Model for each log level but we’ve used
Tuple<string, string> instead to get to the point. The first
string is the name, the second one is the hexadecimal representation of the color.
We could call it like so:
Now, we’d have to create
MainWindow.ConfigureLevels, but let’s create a new
Similarly to the
LogSystem one we created in the previous post, let’s define
Again, let’s have a new
LogLevels list in our
MainWindow to contain all of the levels. And we could populate it from this method:
You’ll understand why we’ve used
Brush instead of
Color in a moment.
Cool! Now we’ve got all of the values in a list. What’s next?
Add log level to LogEntry
Right, until now all of our
LogEntry messages only had a timestamp, a system and the message itself. Now, we need to add the name of the log level it was logged with. I’ll let you go through this one, just add a new
string property and we’re ready.
We’ve established the relationship between log levels. Following our example,
debug < warning < error. So, we only want to select the minimum level we’re showing and the ones over it should show as well. This looks like a radio button to me.
The layout of our window would look like this with this addition:
So, once again, let’s skip some XML attributes and update the layout:
Foreground property expects a
Brush instead of a raw
Color, so that’s the reason why we used one.
Tweaking the sample messages a bit and taking a screenshots looks like this:
As you can see, none of the elements is checked. We’ll work on that soon. But first, let’s add some colors to the messages!
Now that all of the messages have a log level themselves, we could style the
LogEntryList so each row has a color matching the configuration. That way, we can skip adding a new column with the name of the log level.
If all of our log levels and their colors were static resources we’d be defining them in XAML. Since we’re configuring it externally, we must create the styles programmatically.
To do that, let’s update our
MainWindow.ConfigureLevels like so:
Style, as we can see, has the concept of a
DataTrigger. It takes a
Binding and a
Value to test against, and if it matches then the
Setters are applied. In our case just a text color, but we could use other styling. Finally, we assign the whole style to the
ItemContainerStyle, which is the one applied to the
This is the result:
Looking nice, huh? However, there’s something missing, isn’t it?
Alright, alright. We’re happy with the results but we still can’t do anything with these radio buttons! Let’s see what’s left.
We said we’d be adopting the usual each log level has a severity and we can show all of the logs with a higher severity level than the selected one. We’ve got as many radio buttons as log levels, and each one represents a severity (although we aren’t displaying the number itself).
So, it looks like we only want one variable: the currently selected log level. But, how do we go from many radio buttons to a single value?
First of all, let’s create a new variable in
Now, let’s update our
LogLevel class to this:
It looks pretty similar to
LogSystem, doesn’t it?
So, whenever the
LogLevel changes (when the user interacts with the radio buttons) we’ll get notified. And what do we do then?
Selected property will be modified for both the radio button that’s getting selected and the one that’s being deselected, we’re just interested in the former. We update the log level and ask the filter to be re-evaluated.
But, we must add the log level to the filter or it won’t do anything different!
Each message has the name of the log level it was logged with, and we have the severity we want to test against. So, we find out which
LogLevel matches the given name and only keep those messages with a matching or greater severity. Bonus points if you can optimize this to prevent looking for the
And that’s it! Let’s take some screenshots!
Not bad! But can we improve the layout so it looks better?
What about adding more systems and more log levels from the host program? Don’t cheat, you can’t modify the WPF project!
Congratulations, reader! You’ve got a configurable log window of your own! :)
Oh well, I guess that’s it! With this post and the previous one we’ve gone through some useful features: positioning, auto-scrolling, configuring log systems and log levels, filtering… Which other features you’d like to add? I encourage you to do it!
I hope this series helps you create your own version of the log window and use it in the future on your side projects!
In the next post we’ll see how we can have a C++ host program that uses this log window.
Thanks for reading!