Posts by Michaela Joy

    For those of you who grabbed the code in the previous post, you need an update.


    So I started to test the code and I found a few nasty bugs. Hotkeys mysteriously not firing, double commands after closing a dialog box.

    So I spent a few hours going over my design and I came to a realization:

    We need to use the KeyUp event as well as the KeyDown event. We need a way to signal the key handler code to ignore the next keydown / keyup events.

    But only when we are closing a dialog box whose close keystroke is the same as a Hot-key command.

    To start, just add this bool to the top of your form

    1. private bool ProcessingCmd = false;

    Now, here's the new Keydown Event code

    And now... the KeyUp event handler.

    Here's the KeyPress handler, just for completeness...

    Csharp: scintilla KeyPress event Handler
    1. private void TextEditor_KeyPress(object sender,KeyPressEventArgs e)
    2. {
    3. if (char.IsControl(e.KeyChar) == true)
    4. e.Handled = true;
    5. else if (blockCharPress == true)
    6. {
    7. e.Handled = true;
    8. blockCharPress = false;
    9. }
    10. }

    Hi All,

    There are times when I needed to get input from the user, but I didn't want to build a custom dialog box.

    So I came up with a simple solution:


    To use this, you will need to add the following two lines:

    1. using System.Windows.Forms;
    2. using System.Globalization;

    You might also need to add System.Windows.Forms.Design.

    Use is simple.

    Hi All,

    One very nice feature in an application is the ability to respond to keyboard chords. An example of the use of this technique is Visual Studio.

    To toggle regions , you would press <Ctrl+M>,'M'.

    In this post, I'm going to share a (fairly) simple technique to do exactly this. My approach lends itself to being able to be loaded from / saved to an XML file.

    So let's create a list of hotkeys. Let's first start with the definition of a hotkey. For simplicity's sake, I only allowed the second part of the key chord to be a character without a control modifier.

    We'll start with an object that encapsulates a KeyCode, Modifier pair.

    As you can see, the KeyCode and Modifier properties are read only, being set when the object is created.

    Now, let's create a HotKey object.

    Something worth mentioning is the HasCond property. I use it when close program is called,

    because I allow the program to be closed with the <Esc> key.

    If you compile this, you'll get a compiler error. That's because you need to create an enumeration that describes all of the commands that you wish to associate to a hotkey.

    This is what my enum looks like. I'm currently building an editor that uses the ScintillaNET control. Of course, you'll have to add your own definitions.

    Csharp: MJEditorCmds enum
    1. public enum MJEditorCmds {
    2. ecEmpty = 0,ecGotoBookmark0 = 1,ecGotoBookmark1 = 2,ecGotoBookmark2 = 3,ecGotoBookmark3 = 4,ecGotoBookmark4 = 5,
    3. ecGotoBookmark5 = 6,ecGotoBookmark6 = 7,ecGotoBookmark7 = 8, ecGotoBookmark8 = 9,ecGotoBookmark9 = 10,
    4. ecToggleBookmark0 = 11,ecToggleBookmark1 = 12,ecToggleBookmark2 = 13,ecToggleBookmark3 = 14,ecToggleBookmark4 = 15,
    5. ecToggleBookmark5 = 16,ecToggleBookmark6 = 17,ecToggleBookmark7 = 18,ecToggleBookmark8 = 19,ecToggleBookmark9 = 20,
    6. ecToggleBraceMatching = 21,ecToggleParenMatching = 22,ecFileNew = 23,ecFileOpen = 24,ecFileSave = 25,ecFileSaveAs = 26,
    7. ecExitProgram = 27,ecRedo = 28,ecUndo = 29,ecCut = 30,ecCopy = 31,ecPaste = 32,ecFindFirst = 33,ecReplaceFirst = 34,
    8. ecFindReplaceNext = 35,ecInvokeSettings = 36
    9. }

    Now, let's create a hotkey list. Here's my original code. Take a look at the first two definition. The first is a private declaration of our hotkey list.

    The second is a public indexer property. Quite useful if you need to access the object as an array. In this case, it's readonly, but you could easily

    add an on changed event and create a property writer. I'll demonstrate that technique in a post about the color combo box.

    Okay. Now, we have a list of hotkeys. To use them, we need to override both the keydown and keypress event of our form.

    We also need to define a subroutine that will process the Editor command and execute the function, and a bool that will be used to suppress the

    extra keystroke produced when the chord (second) key is pressed.

    Here's the code to do that. I have made significant changes to the way I handle the key events.

    You can find them in this post.

    [C#,.NET] Building a better key handler - Revisited

    Here are the menu events from my form main menu.

    Hi Again,

    In this post, I'm going to talk a little about color formats, and how to do conversions between them. There are a number of color formats,

    some of which only make sense in certain environment. RGBA is a color format that is best suited for digital display devices, in that it provides

    an exact value to display. What you provide is what you get.

    Compare that to HSL, where certain HSL values do not exactly map into the RGBA color space, and vice-versa.

    This can be a problem for graphics designers; you have to know all of the little nuances, and how to get as close as you can to the real color.

    This is the reason I designed MJLoupe. The main lesson I learned here was how little I know about color formats.

    In a previous post, I talked about how RGBA roughly descends from a Windows COLORREF. It is a 32 bit unsigned integer, consisting of 4 bytes. ("AA RR GG BB") In C#, this corresponds to a UInt32.

    Before we can convert this color value to a different format, we must break this number into the 4 bytes that correspond to the Red, Green, Blue, and Alpha values.

    We do this by logical operations. Here's the function, extracted from the MJColorConverter class.

    Once the byte values have been retrieved, you can then begin converting to the desired format.

    As you examine the various color formats, you will notice that the internal calculations are done using floating point variables (C# doubles)

    Also, the color values are scaled to be between 0.0 and 1.0. This is called "Normalization". To cut down on code, I have provided a function that does this "up front", so to speak.

    Here it is.

    Csharp: ToRGBANormalized()
    1. public static void ToRGBANormalized(UInt32 ColorCode,ref double R,ref double G,ref double B, ref double A)
    2. {
    3. int AA = -1,RR = -1,GG = -1,BB = -1;
    4. ToRGBA(ColorCode,ref RR,ref GG,ref BB,ref AA);
    5. A = (double)AA / 255;
    6. R = (double)RR / 255;
    7. G = (double)GG / 255;
    8. B = (double)BB / 255;
    9. }

    Now that we have our color values separated and normalized, we can begin to perform the necessary calculations to convert between formats.

    HSL vs. HSV

    The biggest mistake that every beginning .NET programmer makes is to assume that Color.GetHue(), Color.GetSaturation() and Color.GetBrightness() are HSL.

    They are not. They are HSB, and there's a big difference in the two models.

    Here is one of the first places I went t when I was hunting for info about different color formats.

    Basically, it breaks down into 3 or 4 steps.

    1 ) Collect and normalize the color bytes.

    2 ) Find the maximum and minimum color values. From there, compute the Chroma value. In my code, I use this function, which does all of this for me.

    I'll reuse it later for HSL.

    3 ) Calculate Sat and Value, based on the value of Chroma:

    If Chroma is 0 (i.e the Minimum value is equal to the Maximum value.), then H = S = 0 and V = the Minimum value.

    Otherwise, calculate the H and S values.

    Finally, Scale the output values to our desired range and convert to integer values.

    If you want the values to be percentages in the range [0..1], then use the above calculations.

    NB: I wasn't very happy with the above calculations, as I noticed that they return HSL values. After doing some searching,( to the rescue! )

    I found the correct formula, which seems to produce correct results.

    Here it is

    Now, let's talk about rounding error, and how it affects the output of the conversions.

    A difference in 1/10 can make the difference between a right and wrong answer. Since most of these algorithms return results in the range [0..1],

    You would call this function after you've multiplied by whatever scale was needed.

    Csharp: RoundUp()
    1. private static double RoundUp(double value)
    2. {
    3. int tgt = (int)(value * 10),orig = (int)value,delta = tgt - (orig * 10);
    4. if (delta >= 5)
    5. return (double)(orig + 1);
    6. else
    7. return (double)(orig);
    8. }

    There you have it. RGB to HSV.

    My next post will show some other converters, such as HSL,CMY and CMYK.

    See you then...

    Hi everyone.

    Lets talk about the basic structure of a .NET control.

    There are many other things done in the control constructor. If you want to see the exact code, please download the source.

    I'll post a link when I'm done blathering on about the design.

    Let's talk about the control styles used.

    AllPaintingInWmPaint, OptimizedDoubleBuffer, UserPaint, Opaque, ResizeRedraw

    These five control styles allow us to completely take over all of the drawing in the control. "OptimizedDoubleBuffer" enables double buffering, which reduces flicker when the control is redrawn.

    For this to work, you must also add "AllPaintingInWmPaint","UserPaint" and "Opaque". These flags force all drawing to be performed in the control OnPaint handler, and eliminate background painting.

    For veteran Windows programmers, basically this suppresses WM_ERASEBKGND messages.

    "ResizeRedraw" basically tells the OS to send a WM_PAINT message when the control is resized.

    Notice that "DoubleBuffered" is also set to true. Think of it as insurance against flicker. :)

    If you place this control skeleton into a C# project, it will produce a black square when you put it onto a form. There is no paint handler in place, so that's to be expected.

    Also, we haven't set the dimensions of the control. In my implementation, I have a function which accurately measures the size of a string. I use this to size the control.

    Here it is.

    At some point, I will be adding a TextBox for autocomplete and editing. If you don't add the TextBox to the controls' list of children, the TextBox will never be shown.

    To do that, add this line after you've created the control.

    1. Controls.Add(InplaceEditor);

    Let's talk a little bit about Dispose() and what gets handled there. Anything that gets added to the control child list is automatically disposed of by the control itself.

    But things like timers need to be disposed of by us. There are four timers used in my design. One handles mouse repeat. Another handles tool tips.

    The other two handle cleanup after one of the respective context menus are invoked.

    These timers must be disposed by us. Here's how it's done.

    In the next post, I'll talk about event handlers, and overriding the ones that are important to our control.

    See you then...

    Hi All,

    Sorry I haven't posted in a while, but I've been incredibly busy working on goodies for Bitmight.

    This project started because I wanted a right-click popup menu on the drop-down part of a .NET Combobox control. So I did what I always do:

    I started googling, looking for combobox issues and soon realized that it was all but impossible with the existing combobox control.

    That means a complete rewrite from the ground up. As scary as that sounds, only when the journey is all but complete do you realize how much you've learned.

    I've even gone so far as defining my own color type to simplify conversions and to add a certain amount of convenience.

    So let's talk about colors. There are literally thousands of them, with many of them having descriptive names. (e.g: AliceBlue, PeachPuff, etc.)

    Each color has an associated 32 bit value, based on the Windows COLORREF. The upper 8 bits is called the "Alpha channel". This is followed by the

    Red, Green and Blue bytes respectively (AARRGGBB)

    In a COLORREF, if the upper 8 bits are zero, then the color is fully opaque. A .NET color is a little different: If the Alpha byte is 255, the color is opaque.

    This can be used to our advantage by allowing us to define an 'Empty' or 'Null' Color. This value ends up being 0xFF000000.

    All we have to do is subtract our alpha from 255 to make it compatible with .NET colors. By doing this, our 'Empty' color ends up becoming Color.FromArgb(0,0,0,0), which is an invisible color.

    Let's talk about Windows System colors. To get a System Color requires a level of indirection; the actual values are stored inside the currently installed Windows theme.

    To get the color, we call an exported function called GetSysColor(). This is in User32.dll, so you need to declare it.

    You call it with an index which specifies the system color you want.

    Take a look here:

    Here are the values for the color indices.


    When you call GetSysColor(), you may notice that the colors appear incorrect. As a matter of fact, they are.

    The Red and Blue bytes are swapped. You will have to swap them manually.

    If you're using Win32.cs, this is done for you by calling Win32.CorrectedGetSysColor().

    If not, here's the code.

    I call GetSysColor() every time the user needs a system color. This eliminates the problem of stale colors caused by theme changes.

    More to follow...

    Xenforo v2.0.0 beta 8 has been released. It's still unsupported, so we're not really ready to make the move.

    As soon as v2.0.0 comes out of beta, we'll pull the trigger.

    Thanks for your patience. :)

    QB & MJ

    We're sorry to say that we've been having major issues with Comet Chat. We have removed their product from our forum and are awaiting a refund.

    We apologize to our members for any inconveniences that may have been caused by the lack of a functional chat box.

    Regards, QB and MJ.

    Hi All,

    We are happy to announce that CometChat is currently functioning. There are still a few issues to be worked out, noteably the Whiteboard / Writeboard functionality.

    Please try the chat box, and let us know what you think.

    Regards, QB and MJ.

    Project Status ( 7 August, 2017)

    Here you will find the current status of projects being released. Check back here for updated status and information.


    Current status: Functional.


    Add multi-monitor support.

    Clean up help / About box.

    Hi All,

    This custom control is not the kind of control you'd find on the toolbar. But it has its' uses, especially when creating composite controls (or Property Editors)

    Here it is.

    I spelled it out because I figured I'd explain the code, but here's the source code all ready to use.


    Hi All,

    With integral values, the Property Grid works "right out of the box", so to speak. To represent data in a custom format requires a custom property editor.

    Let's go large and build a custom color property editor. Personally, I find the color editor to be lacking in a few aspects. For one thing, you can't type out the color name and narrow the list.

    And why can't you enter the value in hex?

    To fix this, I designed a color Listbox. (MJColorListbox This control has a few additions that make it better than the original control)

    The ability to enter hex is taken care of in the control form design.

    So let's begin. There are a few pieces to this. First, let's talk about properties.

    Take a look at this code.

    The basic structure of the property is the same as in the last part of the tutorial. We mark the property the same way.

    We have new attributes though. And these are the secrets to creating your custom editor.

    the TypeConverter attribute is used to override the original type editor for that field. We will be designing our own. Put it in a file some where and add it as a link, because it's the same for any project.

    It looks like this.

    To use it, you specify a TypeConverter attribute and point it to the new converter.

    Now, there are some other pieces of code here. They should be in a file somewhere, also to be added to the project by link.

    We have a PreviewChanged event handler, complete with EventArgs. (used in the form code.)

    We also have a paint handler for the grid property. This draws the property when the editor is not visible.

    1. public delegate void MJColorChooserPropertyFormPreviewChangedEventHandler(object sender, MJColorChooserPreviewEventArgs e);
    2. public class MJColorChooserPreviewEventArgs : EventArgs { private Color FPreviewColor; private int FDrawTag; public MJColorChooserPreviewEventArgs(Color AColor,int ADrawTag) { FPreviewColor = AColor; FDrawTag = ADrawTag; } public Color PreviewColor { get {return FPreviewColor;} } public int DrawTag { get { return FDrawTag; }} }
    3. public class MJColorChooserPropertyEditorHelper { public static void ColorChooserPropertyEditorPaintValue(PaintValueEventArgs pe) { string Txt = pe.Value.ToString(); Color theColor = (Color)pe.Value; Rectangle Rct = new Rectangle(pe.Bounds.Left + 1,pe.Bounds.Top + 1,18,pe.Bounds.Height - 2); int i,j; if (theColor.A == 255) { SolidBrush gbr = new SolidBrush(theColor); pe.Graphics.FillRectangle(gbr,Rct); gbr.Dispose(); } else { Bitmap Bmp = new Bitmap(8,8); TextureBrush hbr; for (i = 0;i < 8;i++) { for (j = 0;j < 8;j++) { if ((((i & 1) == 0) && ((j & 1) != 0)) || (((i & 1) != 0) && ((j & 1) == 0))) Bmp.SetPixel(i,j,Color.Black); else Bmp.SetPixel(i,j,Color.White); } } hbr = new TextureBrush(Bmp); pe.Graphics.FillRectangle(hbr,Rct); hbr.Dispose(); Bmp.Dispose(); } } }

    The final part is the property editor itself. It should be in a place where it can see our property class.

    1. public class MJColorChooserPropertyEditor : UITypeEditor { public IWindowsFormsEditorService editorService; public override bool GetPaintValueSupported(ITypeDescriptorContext context){ return true;} public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context) { return UITypeEditorEditStyle.DropDown; } public override void PaintValue(PaintValueEventArgs pe) { MJColorChooserPropertyEditorHelper.ColorChooserPropertyEditorPaintValue(pe); } static void OnColorChooserPreview(object src,MJColorChooserPreviewEventArgs e) { VuTest.Form1.ItSelf.SetUIElementValue(e.DrawTag,e.PreviewColor,true); } public override object EditValue(ITypeDescriptorContext context,IServiceProvider provider,object value) { if (provider != null) editorService = provider.GetService( typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService; if (editorService != null) { int thisDrawTag = -1; MJVuMeterPropInfo VuMeterProp = null; Color theColor = (Color)value; if (context != null) { Object Obj = context.Instance; if (Obj.GetType() == typeof(MJVuMeterPropInfo)) { VuMeterProp = (MJVuMeterPropInfo)Obj; thisDrawTag = (int)VuMeterProp.ModTag; } else return null; MJColorChooserPropertyForm CtrlForm = new MJColorChooserPropertyForm((Color)value,thisDrawTag); CtrlForm.OnPreviewValueChanged += new MJColorChooserPropertyFormPreviewChangedEventHandler(OnColorChooserPreview); CtrlForm.editorService = editorService; editorService.DropDownControl(CtrlForm); CtrlForm.OnPreviewValueChanged -= OnColorChooserPreview; VuTest.Form1.ItSelf.SetUIElementValue((int)thisDrawTag,value,true); if (CtrlForm.Cancelled == false) value = CtrlForm.ColorValue; } } return value; } }

    Okay. Here's the source files for each of these goodies.

    The MJColorChooser Property Form

    The MJColorConverter Type

    The MJColorListBox

    And, MJTypes just for convience.

    Notice the other type editors. I'll post them in the next installment.

    Stay Tuned...

    Hi all,

    For me, the property grid was the most intimidating control in .net. Not intuitive, difficult to set up, seemingly impossible to get results from.

    Until I started to study it. ;)

    For now, let's just understand basic functionality. We'll customize it later.

    Create a Windows Form project, place any control onto the form and place a property grid down next to it.

    Now, select the property grid and go to the property editor. Find the property called "SelectedObject".

    In there, you'll see the other control you put down. Select it and watch what happens to the grid.

    Like magic, it fills all of the properties in and displays them, along with their own editors. :)

    So, what have we learned here? We learned that -any- class attached to the property grid can show its' members.

    This means that we can create our own class, specifically designed to be used with a property grid.

    And now for the problem...How do we tell what property we edited?

    Normally, you'd use the "PropertyValueChanged" event in the property grid to notify you when something in the class changed. But there's no easy way to tell which field in the class was modified.

    Until now.

    It's all in how you design the class used in the property grid.

    First, create an enum, giving each of the class members an index. The first entry should be -1 and marked as Unknown.


    1. public enum MJVuMeterElements {
    2. vueUndefined = -1,vueBaseColor = 0,vueBreak1Color = 1,vueBreak2Color = 2,
    3. vuePeakTickColor = 3,vueShowPeak = 4,vuePeakHold = 5,vuePeakDecay = 6,
    4. vuePeakDropInterval = 7,vuePeakFastDecay = 8,vuePositionScale = 9, vueBreak1Value = 10,vueBreak2Value = 11,vuePosition = 12,vueTimerInterval = 13
    5. }

    This is the enum I created for the MJVuMeter demo.

    So how do we use this? Actually, it's very easy.

    Let's create the class.

    1. public class MJVuMeterPropInfo
    2. {
    3. public MJVuMeterElements ModTag = MJVuMeterElements.vueUndefined;
    4. private Color FBaseColor,FBreak1Color,FBreak2Color,FPeakTickColor;
    5. private bool FShowPeak,FPeakHold;
    6. private int FPeakDecay,FPeakDropInterval,FPeakFastDecay,FPositionScale,FBreak1Value,FBreak2Value,FPosition,FTimerInterval;

    These are the members of the class. notice the public member at the top. ;)

    We'll be using it in our properties here.

    So let's take a look at a property.

    Here is a boolean property. No special editors needed (More on that in a bit)

    The Category attribute is used to group properties together.

    The DisplayName attribute is the actual name of the property in our class. Get this wrong and the property will not show up in the property grid.

    The Description appears in the bottom half of the property grid.

    Notice that we set the ModTag with the value that corresponds to the index of the property in our class. We set it on both the property reader and the writer.

    This means that, when the property grid sets the value, we know exactly which field it's referring to.

    And now the fun begins.

    Here's a trick to have public access to the current instance of any form.

    Add this to the top of your form class.

    1. public partial class Form1 : Form
    2. {
    3. public static Form1 ItSelf = null;
    4. public MJVuMeterPropInfo VuMeterProp = new MJVuMeterPropInfo();

    Also, I've added my Vu Meter editor class at the top and created it.

    Now, let's handle the constructor.

    1. public Form1()
    2. {
    3. InitializeComponent();
    4. ItSelf = this;
    5. pgVuMeter.SelectedObject = VuMeterProp;

    And whatever else you need to add to the Ctor for your own project.

    Now, let's handle the property grid value changes.

    IMHO, the best way to handle setting the values is to use a public function. Put it in the main form. The reason why? If you use this technique in a dialog box, You encapsulate the code.

    Also, if you need to call it from a custom property editor (The next post will demonstrate that...stay tuned) You can use the global form instance (Form1.Itself) and set the class property.

    Here's how I do it in pretty much all of my projects.

    As you can see, the property value will only change when it's not the same as the current value, or if we force the update to take place.

    The return value can be used to alert the user that the data has been modified.

    So let's handle the PropertyValueChanged event and make our changes.

    1. private void pgVuMeter_PropertyValueChanged(object s,PropertyValueChangedEventArgs e)
    2. {
    3. this.SetUIElementValue((int)VuMeterProp.ModTag,e.ChangedItem.Value,false);
    4. // OR: if (this.SetUIElementValue((int)VuMeterProp.ModTag,e.ChangedItem.Value,false) == true)
    5. // Do Modified()
    6. }

    That's it for now. In the next article, I'll show you how to write your own custom property editors.

    Stay Tuned...

    HI All,

    I needed a Vu Meter for MJRadio. So, I decided to build one from scratch. As I said before, building a Windows control is a snap.

    A Vu Meter takes an incoming value (Position), scales it, and determines where the scaled value sits within one of three view regions.

    Default colors were chosen to closely resemble a real Vu Meter display (Digital. Not Analog. That's for another project and another time. :) )

    The lowest region is green (lime) and shows signal at a safe level. The Middle region is yellow and shows the signal right before it clips. The Upper region is Red and shows the signal as it clips.

    The data is scaled to be a percentage of the viewport. The break from the low region to the mid region is at 60%. The break between the middle and the high (clip) region is at 90%.

    The Peak Tick.

    A long time ago, I used WinAmp. Great little program. I noticed that their Vu Meter had a peak tick, which would dance up and down with the music levels.

    So I decided to add that functionality to the control. To do so, I realized that I would have to 'iterate' the control so that the peak tick position would refresh itself.

    Here's the source for the entire control.

    So, much of the code is for properties. The rendering is done in the OnPaint event.

    Here's a list of the properties of MJVuMeter:

    BaseColor: The color of the lower 1/3 of the Vu Meter.
    Break1Color: The color of the middle 1/3 of the Vu Meter.
    Break2Color: The color of the top 1/3 of the Vu Meter.
    PeakTickColor: The color of the Peak Tick.

    ShowPeak (bool): Shows / hides the Peak Tick.
    PeakHold (bool): Holds the Peak Tick at the peak height, or allows IteratePeakDrop() to make it fall.

    PeakDecay (int): The rate that the Peak Tick falls is controlled by this value.\nLarger values make the Peak Tick fall faster.
    PeakDropInterval (int): When <PeakHold> is true, this controlls the time (in seconds) that the peak is held.
    PeakFastDecay (int): This is the rate that the Peak Tick falls at when the control has been reset. Larger values make the Peak Tick fall faster.

    PositionScale (int): The sample scale value.\nPosition is scaled using this value and the Vu meter height.")]

    Break1Value (int): The value at which the color changes from the lower 1/3 to the middle 1/3 of the Vu Meter.
    Break2Value (int): The value at which the color changes from the middle 1/3 to the top 1/3 of the Vu Meter.
    Position (int): The Position (amplitude) of the Vu meter. (0 <= value < PositionScale).

    MJVuMeter methods:

    Reset(): Forces the Vu Meter to reset. Not a very graceful shutdown, but it works.

    IteratePeakDrop(): This method is called periodically in the application to refresh the peak tick position. The radio uses a System timer to refresh the UI, so it's a perfect spot to call it.

    This will cause the peak tick to drop at the rate specified in 'PeakDropInterval'.

    I'm in the process of building a demo project for the Vu Meter. It should be complete by tomorrow. :)

    Edit: Here's the Vu Meter test app. Don't try to rebuild it, as it will be looking for modules that exist in my common code library.

    More about that in another post.

    Happy Coding...

    Some thoughts.

    The lines between sub-forums (#2196f3) needs to be changed to white (#ffffff) for contrast.

    Also, MJLoupe will be useful for determining the color values. Just set the color bar to 'HTML' and it will show you the hex values as well as the color names. ;)

    @QB: The colors in the pic that Raven posted are as follows.

    #292929 for the background.

    #bb4d3e for the banner.

    #223847 for the dark logo.

    Raven : Thanks for posting the link. It allowed me to check MJLoupe and make sure that it's working correctly. :)