Setting up a new Windows 10 Installation

Setting up a windows install can be a pretty involved process. There are a lot of gotchas in the process that I always forget about when setting up a new installation. So here is a post of all the things that I do at the start to serve as a reminder to myself for the things I need to do when I get a new machine.

I intend to have this post serve a living document which I’ll update periodically as I make changes to my Windows 10 installations so I can refer back to it in the future.

Installing Apps

Coding

Gaming

Utilities

Browsers

Chrome Configuration

Disable password management so that LastPass can do it’s thing

Disable notifications on all sites

  • Ublock Origin
  • Lastpass
  • Reddit Enhancement Suite

Firefox Add-ons

Disable password management that LastPass can do it’s thing

Disable notifications on all sites

  • Ublock Origin
  • Lastpass
  • Reddit Enhancement Suite

Configure Windows

  • Turn off Windows Shake
  • Turn off Window Suggestions

Remove Any Bloat I Find Along the Way

Set Chrome as the Default Browser

Remove those annoying inlay hints (annotations) on Jetbrains Rider

Annoyed at seeing all the visual cruft like this?

1. Open up settings

2. In the sidebar, navigate to Editor > Inlay Hints

3. Uncheck “Show hints for:” everything

4. Then navigate to Editor > Inlay Hints > General

5. Then update the following options

  • Uncheck “Enable Inlay Hints in .NET languages
  • Uncheck “Reserve press and hold of the Ctrl key for Push-To-Hint”
  • Update “Default visibility” to “Never Show”

5. Have a much cleaner code experience!

Play Unreal Gold in Ultrawide with High Refresh Rate

It warms my heart when I find that communities are still putting together patches to get older games to run on modern hardware. Old Unreal is an example where we have a patch that gives Unreal support for high refresh rate as well a high ultrawide resolution. Here’s how to add it for yourself:

Prerequisites

  • Unreal Gold is already installed and fully updated. I’m using the version from Steam.
  • You need to have 7-Zip installed. You can find it here

Download the Old Unreal Patcher

Unpack the .7z file you just downloaded

Run the exe UnrealGoldPatch227i.exe

Find the path to your game installation

If you’re using the version that installed from Steam, it’s probably located in: C:\Program Files (x86)\Steam\steamapps\common\Unreal Gold

Select you language

Then hit “Next >”

Put in the path to your Unreal Installation

Then hit “Next >”

Then hit “Next >”

Then hit “Install”

Run Unreal Gold

Make sure to select the OpenGL renderer

Go Into Options > Preferences

Under Video Set Your Preferred Resolution

To Select a High Refresh Rate, Navigate to Options > Advanced Options

Under Advanced Options Navigate to Rendering > OpenGL Support > FrameRateLimit and set the value to the desired amount
Close that window and you’re done!

How to Create Global Keyboard Hook with C# in Linux

For my intents and purposes, I wanted to have some code that would publish an event any time the user pressed a key anywhere on the system. While developing this, I found that I could also hook into mouse events as well.

It’s important to note that the code here (due to the Linux) doesn’t really distinguish between a keyboard button press and a mouse button press. To Linux, they’re both just buttons.

Understanding that you can actually expand this code to work with other items like gamepads and special input peripherals if you desire.

Additional Gotchas – This code here will just listen to events, but won’t block them from being consumed by the rest of the operating system. This means that if you want an event handler to the power off button or volume buttons you’ll run into issues with other processes consuming those updates.

Setting Up Permissions

In order to run this code, the user that runs this program will have to be in the input user group otherwise it will throw an exception. Run this command to add the current user to that group.

sudo gpasswd -a $USER input

EventType.cs

Since the folder /dev/input is essentially an event bus of a bunch of input/output devices for the Linux, there are a variety of event types that you may want to consume. Here is the enumeration to make deciphering the event types a little easier.

public enum EventType
{
    /// <summary>
    /// Used as markers to separate events. Events may be separated in time or in space, such as with the multitouch protocol.
    /// </summary>
    EV_SYN,

    /// <summary>
    /// Used to describe state changes of keyboards, buttons, or other key-like devices.
    /// </summary>
    EV_KEY,

    /// <summary>
    /// Used to describe relative axis value changes, e.g. moving the mouse 5 units to the left.
    /// </summary>
    EV_REL,

    /// <summary>
    /// Used to describe absolute axis value changes, e.g. describing the coordinates of a touch on a touchscreen.
    /// </summary>
    EV_ABS,

    /// <summary>
    /// Used to describe miscellaneous input data that do not fit into other types.
    /// </summary>
    EV_MSC,

    /// <summary>
    /// Used to describe binary state input switches.
    /// </summary>
    EV_SW,

    /// <summary>
    /// Used to turn LEDs on devices on and off.
    /// </summary>
    EV_LED,

    /// <summary>
    /// Used to output sound to devices.
    /// </summary>
    EV_SND,

    /// <summary>
    /// Used for autorepeating devices.
    /// </summary>
    EV_REP,

    /// <summary>
    /// Used to send force feedback commands to an input device.
    /// </summary>
    EV_FF,

    /// <summary>
    /// A special type for power button and switch input.
    /// </summary>
    EV_PWR,

    /// <summary>
    /// Used to receive force feedback device status.
    /// </summary>
    EV_FF_STATUS,
}

KeyState.cs

Like many other event handling systems there are multiple events that happen each time that the user presses a key. Once when the key is pressed down, another when the key is pressed up, and another if the user decides to hold the key down.

public enum KeyState
{
    KeyUp,
    KeyDown,
    KeyHold
}

EventCode.cs

Each distinct button is associated with an event code. Whether it’s a button on a keyboard or a button on a mouse, you’ll probably be able to find it here. He is a helper enum class to make deciphering those codes easier.

/// <summary>
/// Mapping for this can be found here: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
/// </summary>
public enum EventCode
{
    Reserved = 0,
    Esc = 1,
    Num1 = 2,
    Num2 = 3,
    Num3 = 4,
    Num4 = 5,
    Num5 = 6,
    Num6 = 7,
    Num7 = 8,
    Num8 = 9,
    Num9 = 10,
    Num0 = 11,
    Minus = 12,
    Equal = 13,
    Backspace = 14,
    Tab = 15,
    Q = 16,
    W = 17,
    E = 18,
    R = 19,
    T = 20,
    Y = 21,
    U = 22,
    I = 23,
    O = 24,
    P = 25,
    LeftBrace = 26,
    RightBrace = 27,
    Enter = 28,
    LeftCtrl = 29,
    A = 30,
    S = 31,
    D = 32,
    F = 33,
    G = 34,
    H = 35,
    J = 36,
    K = 37,
    L = 38,
    Semicolon = 39,
    Apostrophe = 40,
    Grave = 41,
    LeftShift = 42,
    Backslash = 43,
    Z = 44,
    X = 45,
    C = 46,
    V = 47,
    B = 48,
    N = 49,
    M = 50,
    Comma = 51,
    Dot = 52,
    Slash = 53,
    RightShift = 54,
    KpAsterisk = 55,
    LeftAlt = 56,
    Space = 57,
    Capslock = 58,
    F1 = 59,
    Pf2 = 60,
    F3 = 61,
    F4 = 62,
    F5 = 63,
    F6 = 64,
    F7 = 65,
    F8 = 66,
    Pf9 = 67,
    F10 = 68,
    Numlock = 69,
    ScrollLock = 70,
    Kp7 = 71,
    Kp8 = 72,
    Kp9 = 73,
    PkpMinus = 74,
    Kp4 = 75,
    Kp5 = 76,
    Kp6 = 77,
    KpPlus = 78,
    Kp1 = 79,
    Kp2 = 80,
    Kp3 = 81,
    Kp0 = 82,
    KpDot = 83,

    Zenkakuhankaku = 85,
    //102ND = 86,
    F11 = 87,
    F12 = 88,
    Ro = 89,
    Katakana = 90,
    Hiragana = 91,
    Henkan = 92,
    Katakanahiragana = 93,
    Muhenkan = 94,
    KpJpComma = 95,
    KpEnter = 96,
    RightCtrl = 97,
    KpSlash = 98,
    SysRq = 99,
    RightAlt = 100,
    LineFeed = 101,
    Home = 102,
    Up = 103,
    Pageup = 104,
    Left = 105,
    Right = 106,
    End = 107,
    Down = 108,
    Pagedown = 109,
    Insert = 110,
    Delete = 111,
    Macro = 112,
    Mute = 113,
    VolumeDown = 114,
    VolumeUp = 115,
    Power = 116, // SC System Power Down
    KpEqual = 117,
    KpPlusMinus = 118,
    Pause = 119,
    Scale = 120, // AL Compiz Scale (Expose)

    KpComma = 121,
    Hangeul = 122,
    Hanja = 123,
    Yen = 124,
    LeftMeta = 125,
    RightMeta = 126,
    Compose = 127,

    Stop = 128, // AC Stop
    Again = 129,
    Props = 130, // AC Properties
    Undo = 131, // AC Undo
    Front = 132,
    Copy = 133, // AC Copy
    Open = 134, // AC Open
    Paste = 135, // AC Paste
    Find = 136, // AC Search
    Cut = 137, // AC Cut
    Help = 138, // AL Integrated Help Center
    Menu = 139, // Menu (show menu)
    Calc = 140, // AL Calculator
    Setup = 141,
    Sleep = 142, // SC System Sleep
    Wakeup = 143, // System Wake Up
    File = 144, // AL Local Machine Browser
    Sendfile = 145,
    DeleteFile = 146,
    Xfer = 147,
    Prog1 = 148,
    Prog2 = 149,
    Www = 150, // AL Internet Browser
    MsDos = 151,
    Coffee = 152, // AL Terminal Lock/Screensaver
    RotateDisplay = 153, // Display orientation for e.g. tablets
    CycleWindows = 154,
    Mail = 155,
    Bookmarks = 156, // AC Bookmarks
    Computer = 157,
    Back = 158, // AC Back
    Forward = 159, // AC Forward
    CloseCd = 160,
    EjectCd = 161,
    EjectCloseCd = 162,
    NextSong = 163,
    PlayPause = 164,
    PreviousSong = 165,
    StopCd = 166,
    Record = 167,
    Rewind = 168,
    Phone = 169, // Media Select Telephone
    Iso = 170,
    Config = 171, // AL Consumer Control Configuration
    Homepage = 172, // AC Home
    Refresh = 173, // AC Refresh
    Exit = 174, // AC Exit
    Move = 175,
    Edit = 176,
    ScrollUp = 177,
    ScrollDown = 178,
    KpLeftParen = 179,
    KpRightParen = 180,
    New = 181, // AC New
    Redo = 182, // AC Redo/Repeat
    
    F13 = 183,
    F14 = 184,
    F15 = 185,
    F16 = 186,
    F17 = 187,
    F18 = 188,
    F19 = 189,
    F20 = 190,
    F21 = 191,
    F22 = 192,
    F23 = 193,
    F24 = 194,
    
    PlayCd = 200,
    PauseCd = 201,
    Prog3 = 202,
    Prog4 = 203,
    Dashboard = 204,    // AL Dashboard
    Suspend = 205,
    Close = 206,    // AC Close
    Play = 207,
    FastForward = 208,
    BassBoost = 209,
    Print = 210,    // AC Print
    Hp = 211,
    Camera = 212,
    Sound = 213,
    Question = 214,
    Email = 215,
    Chat = 216,
    Search = 217,
    Connect = 218,
    Finance = 219,  // AL Checkbook/Finance
    Sport = 220,
    Shop = 221,
    AltErase = 222,
    Cancel = 223,   // AC Cancel
    BrightnessDown = 224,
    BrightnessUp = 225,
    Media = 226,
    
    SwitchVideoMode = 227,  // Cycle between available video outputs (Monitor/LCD/TV-out/etc)
    KbdIllumToggle = 228,
    KbdIllumDown = 229,
    KbdIllumUp = 230,

    Send = 231, // AC Send
    Reply = 232,    // AC Reply
    ForwardMail = 233,  // AC Forward Msg
    Save = 234, // AC Save
    Documents = 235,

    Battery = 236,

    Bluetooth = 237,
    Wlan = 238,
    Uwb = 239,

    Unknown = 240,

    VideoNext = 241,    // drive next video source
    VideoPrev = 242,    // drive previous video source
    BrightnessCycle = 243,  // brightness up, after max is min
    BrightnessAuto = 244,   // Set Auto Brightness: manual brightness control is off, rely on ambient
    DisplayOff = 245,   // display device to off state

    Wwan = 246, // Wireless WAN (LTE, UMTS, GSM, etc.)
    RfKill = 247,   // Key that controls all radios

    MicMute = 248,  // Mute / unmute the microphone
    LeftMouse = 272,
    RightMouse = 273,
    MiddleMouse = 274,
    MouseBack = 275,
    MouseForward = 276,
    
    ToolFinger = 325,
    ToolQuintTap = 328,
    Touch = 330,
    ToolDoubleTap = 333, 
    ToolTripleTap = 334, 
    ToolQuadTap = 335,
    Mic = 582
}

MouseAxis.cs

Mouse movements are expressed in an amount moved and an axis associated with that change. 0 represents movements on the X axis and 1 represents movements on the Y axis.

public enum MouseAxis
{
    X,
    Y
}

KeypressEvent.cs

Here is the event that I use to process key press events.

public class KeyPressEvent : EventArgs
{
    public KeyPressEvent(EventCode code, KeyState state)
    {
        Code = code;
        State = state;
    }

    public EventCode Code { get; }
    
    public KeyState State { get; }
}

MouseMoveEvent.cs

Here is the event that I use process mouse movement change updates.

public class MouseMoveEvent : EventArgs
{
    public MouseMoveEvent(MouseAxis axis, int amount)
    {
        Axis = axis;
        Amount = amount;
    }
    
    public MouseAxis Axis { get; }
    
    public int Amount { get; set; }
}

InputReader.cs

This is where the bulk of the work happens. Here we have a class, where you provide the path to one of the event files and it publishes updates whenever it comes in. An example file that does this would be “/dev/input/event0”.

More research would be needed to support more events types, but I was only interested in keyboard and mouse input so it serves my purposes. I also opted to drop the timestamp that is included with each button event, but if you’re interested, you can find it on the first 16 bits on the buffer.

public class InputReader : IDisposable
{
    public delegate void RaiseKeyPress(KeyPressEvent e);

    public delegate void RaiseMouseMove(MouseMoveEvent e);

    public event RaiseKeyPress OnKeyPress;
    public event RaiseMouseMove OnMouseMove;

    private const int BufferLength = 24;
    
    private readonly byte[] _buffer = new byte[BufferLength];
    
    private FileStream _stream;
    private bool _disposing;

    public InputReader(string path)
    {
        _stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

        Task.Run(Run);
    }

    private void Run()
    {
        while (true)
        {
            if (_disposing)
                break;

            _stream.Read(_buffer, 0, BufferLength);

            var type = BitConverter.ToInt16(new[] {_buffer[16], _buffer[17]}, 0);
            var code = BitConverter.ToInt16(new[] {_buffer[18], _buffer[19]}, 0);
            var value = BitConverter.ToInt32(new[] {_buffer[20], _buffer[21], _buffer[22], _buffer[23]}, 0);

            var eventType = (EventType) type;

            switch (eventType)
            {
                case EventType.EV_KEY:
                    HandleKeyPressEvent(code, value);
                    break;
                case EventType.EV_REL:
                    var axis = (MouseAxis) code;
                    var e = new MouseMoveEvent(axis, value);
                    OnMouseMove?.Invoke(e);
                    break;
            }
        }
    }

    private void HandleKeyPressEvent(short code, int value)
    {
        var c = (EventCode) code;
        var s = (KeyState) value;
        var e = new KeyPressEvent(c, s);
        OnKeyPress?.Invoke(e);
    }

    public void Dispose()
    {
        _disposing = true;
        _stream.Dispose();
        _stream = null;
    }
}

AggregateInputReader.cs

I’m looking to handle input from every device anywhere on the system. I’ve put together this class to aggregate the input events from all the files in the “/dev/input” folder.

Known issue – This code will throw an exception if a usb device is removed while it’s running. I do intend to fix it in my own app implementation, but I don’t have time to take care of it now.

public class AggregateInputReader : IDisposable
{
    private List<InputReader> _readers = new();
    
    public event InputReader.RaiseKeyPress OnKeyPress;

    public AggregateInputReader()
    {
        var files = Directory.GetFiles("/dev/input/", "event*");

        foreach (var file in files)
        {
            var reader = new InputReader(file);
            
            reader.OnKeyPress += ReaderOnOnKeyPress;

            _readers.Add(reader);
        }
    }

    private void ReaderOnOnKeyPress(KeyPressEvent e)
    {
        OnKeyPress?.Invoke(e);
    }

    public void Dispose()
    {
        foreach (var d in _readers)
        {
            d.OnKeyPress -= ReaderOnOnKeyPress;
            d.Dispose();
        }

        _readers = null;
    }
}

Example Usage

Not bad that this can now be accomplished in two line of code.

public class Program
{
    public static void Main(string[] args)
    {
        using var aggHandler = new AggregateInputReader();

        aggHandler.OnKeyPress += (e) => { System.Console.WriteLine($"Code:{e.Code} State:{e.State}"); };

        System.Console.ReadLine();
    }
}

Thanks for sticking with this. I hope it works out for you!

Today I Learned – I Can Customize my Profile on Github

I’m sure I’m late to finding this out, but it turns out that you can have a custom bio on your Github profile.

Customize your own github profile

1. Create a new repository

2. Name the repository the same name as your profile

3. Add a README.md file to the repo

That’s It

I’m sure there is a lot more that I can do with customization, but at the very least I wanted a short description and link back to this blog.

Listing Input Devices with C# in Linux

I’m going on a journey to build a remote hub for my home theater using an Arduino and my C# programming skills. Part of that story is I wanted to be able to get input from a wireless USB remote that I purchased on amazon.

I’m still not entirely certain that this is even possible, but one part of the process is being able to list the devices that Linux has connected as input devices.

The first step was finding out that there is a file located at: “/proc/bus/input/devices” which contains a list of devices and details about how to connect to them.

Here is the screen of the output when I run the command: “sudo cat /proc/bus/input/devices”

My response from here is to build out the code to map this data to a c# class so that I can use it in other places. I was able to get a really good breakdown of the construction of the data here based on this stack overflow posting.

To start this, I built out a POCO objects:

public class LinuxDeviceIdentifier
 {
     public string Bus { get; set; }
     public string Vendor { get; set; }
     public string Product { get; set; }
     public string Version { get; set; }
 } 

public class LinuxDevice
 {
     public LinuxDeviceIdentifier Identifier = new();
     public string Name { get; set; }
     public string PhysicalPath { get; set; }
     public string SysFsPath { get; set; }
     public string UniqueIdentificationCode { get; set; }
     public List<string> Handlers = new();
     public List<string> Bitmaps = new();
 }

Now that I have the objects to map to I’m able to build out the functionality to read the file. This is what I came up with:

public class DeviceManager
    {
        public static IEnumerable<LinuxDevice> Get(string path = "/proc/bus/input/devices")
        {
            var devices = new List<LinuxDevice>();

            using var filestream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            using var reader = new StreamReader(filestream);
            
            var linuxDevice = new LinuxDevice();
            while (!reader.EndOfStream)
            {
                var line = reader.ReadLine();

                if (string.IsNullOrWhiteSpace(line))
                {
                    if (!string.IsNullOrWhiteSpace(linuxDevice.Name))
                    {
                        devices.Add(linuxDevice);
                        linuxDevice = new LinuxDevice();    
                    }
                    
                    continue;
                }

                if (line.StartsWith("I"))
                    ApplyIdentifier(line, linuxDevice);
                    
                else if (line.StartsWith("N")) 
                    linuxDevice.Name = line.Substring(9, line.Length - 9 - 1);
                    
                else if (line.StartsWith("P"))
                    linuxDevice.PhysicalPath = line[8..];
                
                else if (line.StartsWith("S")) 
                    linuxDevice.SysFsPath = line[9..];
                
                else if (line.StartsWith("U")) 
                    linuxDevice.UniqueIdentificationCode = line[8..];
                
                else if (line.StartsWith("H")) 
                    linuxDevice.Handlers = line[12..]
                        .Split(" ")
                        .Where(h => !string.IsNullOrWhiteSpace(h))
                        .ToList();
                
                else if (line.StartsWith("B"))
                    linuxDevice.Bitmaps.Add(line[3..]);
            }

            return devices;
        }

        private static void ApplyIdentifier(string line, LinuxDevice linuxDevice)
        {
            var values = line[3..]
                .Split(" ");

            foreach (var v in values)
            {
                var kvp = v.Split("=");

                switch (kvp[0])
                {
                    case "Bus":
                        linuxDevice.Identifier.Bus = kvp[1];
                        break;
                    case "Vendor":
                        linuxDevice.Identifier.Vendor = kvp[1];
                        break;
                    case "Product":
                        linuxDevice.Identifier.Product = kvp[1];
                        break;
                    case "Version":
                        linuxDevice.Identifier.Version = kvp[1];
                        break;
                }
            }
        }
    }

Please Note: In order to run this code you either need to run as Root or run as a user who is in the input group in linux for security reasons.

Thanks for checking this post out. I hope it helped you out!

Be a Faster Developer with Switcheroo

There are many things that Windows does right from a productivity standpoint:

  • Great keyboard shortcuts for basic window management
  • Quick application launching by just hitting the windows key and typing the app name (when it wants to work…)

When it comes to development, I try to go through the process of trying to at least consider every chance to improve my workflow even if it’s only by 1%. The theory here is that if I keep on compounding 1% improvements I still end up with an exponential graph of my productivity over time.

One such tool that falls under the category of 1% better is Switcheroo.  Switcheroo is a really simple app that allows you to focus the application by simply typing the name. I fully accept that OS X and Linux have had this feature for a long time so feel free to ignore this post.

How to use this

  • Alt+Spacebar – Bring up the window
  • Enter – Focus the application selected on the list
  • Esc – Close the switcheroo window
  • Ctrl+W – Close the application

That is all there is to it. Nothing too fancy.

I am of the philosophy that I can speed up the way that I work if I avoid taking my hands off the keyboard. With Switcheroo, I don’t need to move my hand over to the mouse and hunt around for the application I want to focus. After using this application for years, using the Windows taskbar just feels slow and clunky.

It’s not perfect. There are some things you should know before using the app. As far as I know, there is no active development for this. This may or may not be a problem for you. The app functions well enough. The only bug that I’ve encountered is that it crashes when trying to bring up an icon from an app that is running from a UNC path.

I would still like to see some active development on this project because it would also be nice to see it get migrated over to .NET 5. Unfortunately, I don’t think I have the spare cycles at the moment to take it over.

Adding a Selected Row Feature to a Table in ASP.NET Blazor

For the bulk of my career, I’ve developed WinForms/WPF applications in windows. Blazor is exciting for me because it allows me to create apps that are as dynamic as the ones I build for thick client applications.

One such pattern that I like to use when building CRUD applications is the ability to select a row by clicking it. This is useful for building apps that use the master-detail UX pattern. Currently, when I google for a solution to this, I only get examples for Syncfusion’s datagrid. I have nothing against their datagrid, but I don’t think a simple master-detail view should require a paid UI framework.

The blog post here will contain just the relevant snippets to explain how to do this. If you’re interested in testing out a working demo you can pull down the code for the solution here: https://github.com/GrantByrne/BlazorSelectedRowExample

To start this off, I’ll create a model to represent an employee:

public class Employee
{
    public Guid Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public int Age { get; set; }

    public string Address { get; set; }
    
    public string City { get; set; }

    public string State { get; set; }

    public string ZipCode { get; set; }
}

To generate something realistic sample data, I’m pulling in a NuGet package called Faker.Net. The populates all the fields that need somewhat believable data.

public class EmployeeService
{
    public Task<Employee[]> GetEmployeesAsync()
    {
        var employeeCount = 10;

        var employees = new Employee[employeeCount];
        for(var x = 0; x < employeeCount; x++)
        {
            var employee = new Employee();

            employee.Id = Guid.NewGuid();
            employee.FirstName = Faker.Name.First();
            employee.LastName = Faker.Name.Last();
            employee.Age = Faker.RandomNumber.Next(18, 100);
            employee.Address = Faker.Address.StreetAddress();
            employee.City = Faker.Address.City();
            employee.State = Faker.Address.UsState();
            employee.ZipCode = Faker.Address.ZipCode();

            employees[x] = employee;
        }

        return Task.FromResult(employees);
    }
}

Next to build out the UI:

@using BlazorSelectedRowExample.Data
@page "/"

<h1>Employees</h1>

<div class="row">
    <div class="col">
        <table class="table table-striped table-hover">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Location</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var employee in _employees)
                {
                    <tr @onclick="() => Select(employee)">
                        <td>
                            @employee.LastName, @employee.FirstName
                        </td>
                        <td>
                            @employee.City, @employee.State
                        </td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
    @if (_selectedEmployee != null)
    {
        <div class="col">
            <ul>
                <li><b>Id</b> - @_selectedEmployee.Id</li>
                <li><b>First Name</b> - @_selectedEmployee.FirstName</li>
                <li><b>Last Name</b> - @_selectedEmployee.LastName</li>
                <li><b>Age</b> - @_selectedEmployee.Age</li>
                <li><b>Address</b> - @_selectedEmployee.Address</li>
                <li><b>City</b> - @_selectedEmployee.City</li>
                <li><b>State</b> - @_selectedEmployee.State</li>
                <li><b>Zip Code</b> - @_selectedEmployee.ZipCode</li>
            </ul>
        </div>
    }
</div>

@code
{
    private Employee[] _employees;
    private Employee _selectedEmployee;
    private readonly EmployeeService _employeeService = new EmployeeService();

    protected override async Task OnInitializedAsync()
    {
        _employees = await _employeeService.GetEmployeesAsync();
    }

    private void Select(Employee employee)
    {
        _selectedEmployee = employee;
    }
}

The UI is split up into two columns. The left column is a list of employees with some brief details. The right column provides details on a specific employee.


When the view is initialized, it loads up the full list of employees in a class level array to build out the table. I’ve added an event handler for the Blazor OnClick event which sets another class level variable for when an employee is focused. It only displays the details about the employee when that person is selected.

And Here’s What it Looks Like

Thanks for reading to the end. I hope this helps you out in your Blazor development.

Setting Window Size With Caliburn Micro

This is something that has actually bugged me for a while. Once I figured it out, it annoyed me that I didn’t figure it out sooner.

When displaying a window in Caliburn.Micro, you can set attributes about the Window object when calling it.

So, let’s say you want to set the height and width on the window to 600 x 300:

First, you would start with something like this:

public class ShellViewModel : PropertyChangedBase, IShell
{
    private readonly IWindowManager windowManager;

    public ShellViewModel()
    {
        this.windowManager = new WindowManager();
        this.windowManager.ShowWindow(new LameViewModel());
    }
}

There are two other fields on the ShowWindow method. The third parameter lets you dynamically set the attributes on the Window object.

public class ShellViewModel : PropertyChangedBase, IShell
{
    private readonly IWindowManager windowManager;

    public ShellViewModel()
    {
        this.windowManager = new WindowManager();

        dynamic settings = new ExpandoObject();
        settings.Height = 600;
        settings.Width = 300;

        this.windowManager.ShowWindow(new LameViewModel(), null, settings);
    }
}

I wish there was more information about working with this on the documentation, but there you have it.

Wiring Up Fluent Validation to WPF

Update

I originally wrote this proof of concept about 7 years ago early in my time with WPF. I published it on an earlier iteration of my blog via Github Gist and WordPress (You can find the gist here). After which point I pretty much forgot out it.

I recently came across the Gist and found that it has helped out a surprising number of people. So, it makes sense to me to pull this in to a blog and annotate it a bit better. If there is interest, I may make some more changes to streamline to examples and take advantage of newer C# features.

Overview

Fluent Validation is my favorite validation library for C#. It is pretty straightforward to use and it forces you to separate out the validation code into a separate class which IMHO generally makes the code cleaner.

The library includes some pretty standard integrations with ASP.NET, but there never was a first class implementation that integrates a validator with a WPF view model. This post is a proof of concept that I put together to bridge that gap.

Building the Validator

For the purposes of demonstration. We’ll have a UserViewModel which has a property for Name, E-Mail, and Zip Code. So we’ll write a quick validator for the usual aspects of that data.

using System.Text.RegularExpressions;
using FluentValidation;
using WpfFluentValidationExample.ViewModels;

namespace WpfFluentValidationExample.Lib
{
    public class UserValidator : AbstractValidator<UserViewModel>
    {
        public UserValidator()
        {
            RuleFor(user => user.Name)
                .NotEmpty()
                .WithMessage("Please Specify a Name.");

            RuleFor(user => user.Email)
                .EmailAddress()
                .WithMessage("Please Specify a Valid E-Mail Address");

            RuleFor(user => user.Zip)
                .Must(BeAValidZip)
                .WithMessage("Please Enter a Valid Zip Code");
        }

        private static bool BeAValidZip(string zip)
        {
            if (!string.IsNullOrEmpty(zip))
            {
                var regex = new Regex(@"\d{5}");
                return regex.IsMatch(zip);
            }
            return false;
        }
    }
}

Creating the View to Present the Validation

Here is the view that I created to demonstrate this implementation.

This is a standard form written in XAML with a few differences:

  • In the property binding for the text on the textboxes we can see that I added a “ValidatesOnDataErrors=True” clause.
  • On each one of those textboxes as well, I expanded out the Validation.Error template property with a stack panel which will slot in a validation error when one occurs.
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewModels="clr-namespace:WpfFluentValidationExample.ViewModels" x:Class="WpfFluentValidationExample.Views.UserView"
        Title="UserView" Height="300" MinWidth="500">
    <Window.DataContext>
        <viewModels:UserViewModel/>
    </Window.DataContext>
    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <Label Content="Name" Margin="10"/>
            <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Width="200" Margin="10">
                <Validation.ErrorTemplate>
                    <ControlTemplate>
                        <StackPanel Orientation="Horizontal">
                            <AdornedElementPlaceholder x:Name="textBox"/>
                            <TextBlock Margin="10" Text="{Binding [0].ErrorContent}" Foreground="Red"/>
                        </StackPanel>
                    </ControlTemplate>
                </Validation.ErrorTemplate>
            </TextBox>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <Label Content="E-Mail" Margin="10"/>
            <TextBox Text="{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Width="200" Margin="10">
                <Validation.ErrorTemplate>
                    <ControlTemplate>
                        <StackPanel Orientation="Horizontal">
                            <AdornedElementPlaceholder x:Name="textBox"/>
                            <TextBlock Margin="10" Text="{Binding [0].ErrorContent}" Foreground="Red"/>
                        </StackPanel>
                    </ControlTemplate>
                </Validation.ErrorTemplate>
            </TextBox>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <Label Content="Zip" Margin="10"/>
            <TextBox Text="{Binding Zip, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Width="200" Margin="10">
                <Validation.ErrorTemplate>
                    <ControlTemplate>
                        <StackPanel Orientation="Horizontal">
                            <!-- Placeholder for the TextBox itself -->
                            <AdornedElementPlaceholder x:Name="textBox"/>
                            <TextBlock Margin="10" Text="{Binding [0].ErrorContent}" Foreground="Red"/>
                        </StackPanel>
                    </ControlTemplate>
                </Validation.ErrorTemplate>
            </TextBox>
        </StackPanel>
        <Button Margin="10">Submit</Button>
    </StackPanel>
</Window>

The Code Behind on the View

Including this for the sake of completeness. Please ignore.

using System.Windows;
using WpfFluentValidationExample.ViewModels;

namespace WpfFluentValidationExample.Views
{
    /// <summary>
    /// Interaction logic for UserView.xaml
    /// </summary>
    public partial class UserView : Window
    {
        public UserView()
        {
            InitializeComponent();
            DataContext = new UserViewModel();
        }
    }
}

The Viewmodel

The viewmodel is the integration point between the fluent validator, the data, and the view. Initially this looks like a standard viewmodel. We have a property for the name, email, and zip code.

Closer to the bottom, you’ll find the integration for the validation:

  • There is the integration of the overloaded [] operator which matches up the property name with the validation.
  • There is also the addition of an Error string which combines together all the errors into a single string
using System;
using System.ComponentModel;
using System.Linq;
using WpfFluentValidationExample.Lib;

namespace WpfFluentValidationExample.ViewModels
{
    public class UserViewModel : INotifyPropertyChanged, IDataErrorInfo
    {
        private readonly UserValidator _userValidator = new UserValidator();

        private string _zip;
        private string _email;
        private string _name;

        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged("Name");
            }
        }

        public string Email
        {
            get { return _email; }
            set
            {
                _email = value;
                OnPropertyChanged("Email");
            }
        }

        public string Zip
        {
            get { return _zip; }
            set
            {
                _zip = value;
                OnPropertyChanged("Zip");
            }
        }

        public string this[string columnName]
        {
            get
            {
                var firstOrDefault = _userValidator.Validate(this).Errors.FirstOrDefault(p => p.PropertyName == columnName);
                if (firstOrDefault != null)
                    return _userValidator != null ? firstOrDefault.ErrorMessage : "";
                return "";
            }
        }

        public string Error
        {
            get
            {
                if (_userValidator != null)
                {
                    var results = _userValidator.Validate(this);
                    if (results != null && results.Errors.Any())
                    {
                        var errors = string.Join(Environment.NewLine, results.Errors.Select(x => x.ErrorMessage).ToArray());
                        return errors;
                    }
                }
                return string.Empty;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Conclusion

Thanks for sticking around to the end. I hope this helped you out improving the validation on your WPF application.

If this helped you out, it would be helpful to star the Github Gist that is linked at the top of the post so that I know that I’m helping people out.