# Mono + GTK# + Glade — WSL Setup Guide

## Table of Contents
- [Mono](#mono)
  - [1. Prerequisites](#1-prerequisites)
  - [2. Install Mono](#2-install-mono)
  - [3. Install GTK# (GTK# 3.0, for Ubuntu 24.04)](#3-install-gtk-gtk-30-for-ubuntu-2404)
  - [4. Install Glade (visual UI designer)](#4-install-glade-visual-ui-designer)
  - [5. Optional: libgdiplus (only if using System.Drawing)](#5-optional-libgdiplus-only-if-using-systemdrawing)
  - [6. Compiling & Running](#6-compiling--running)
  - [7. Minimal Working Examples](#7-minimal-working-examples)
  - [8. Custom Output Names, Targets & Exporting DLLs](#8-custom-output-names-targets--exporting-dlls)
  - [9. Common Errors & Fixes](#9-common-errors--fixes)
  - [10. Notes on Alternatives](#10-notes-on-alternatives)
- [OS](#os)
  - [Workflow](#workflow)
  - [OS images checked](#os-images-checked)
  - [OS images to check](#os-images-to-check)
- [EARS Application](#ears-application)
  - [11. Project Layout](#11-project-layout)
  - [12. Single-Window Notebook Architecture](#12-single-window-notebook-architecture)
  - [13. Glade Rules That Bite](#13-glade-rules-that-bite)
  - [14. Generating Widgets From Code](#14-generating-widgets-from-code)
  - [15. Loading cases.csv](#15-loading-casescsv)
  - [16. File Paths — AppFile()](#16-file-paths--appfile)
  - [17. Audio Playback](#17-audio-playback)
  - [18. Build & Run](#18-build--run)
  - [19. Debugging Autoconnect Failures](#19-debugging-autoconnect-failures)
  - [20. Windows Deployment Differences](#20-windows-deployment-differences)


---

## Mono

### 1. Prerequisites
- Windows 11 (or updated Windows 10) with WSL2 and **WSLg** enabled for GUI support
- Ubuntu 24.04 (noble) or similar WSL distro

Check WSLg is active:
```bash
echo $DISPLAY
```
Should return something like `:0`. If empty, GUI apps won't display.

---

### 2. Install Mono

```bash
sudo apt update
sudo apt install mono-complete
```

Verify:
```bash
mono --version
mcs --version
```

---

### 3. Install GTK# (GTK# 3.0, for Ubuntu 24.04)

> Note: `gtk-sharp2` is **not available** on Ubuntu 24.04 (noble). Use `gtk-sharp3` instead.

```bash
sudo add-apt-repository universe
sudo apt update
sudo apt install gtk-sharp3
```

Verify the package is registered:
```bash
pkg-config --list-all | grep gtk-sharp
```
Expected output:
gtk-sharp-3.0 Gtk - Gtk
---

### 4. Install Glade (visual UI designer)

```bash
sudo apt install glade
```

Launch it:
```bash
glade
```
Design your UI visually, then save as `yourfile.glade` (XML format).

---

### 5. Optional: libgdiplus (only if using System.Drawing)

Only needed if your code uses `System.Drawing.Bitmap`, `Graphics`, etc. (not required for plain GTK#/Glade apps):

```bash
sudo apt install libgdiplus
```

---

### 6. Compiling & Running

#### Plain console C# program
```bash
mcs myprogram.cs
mono myprogram.exe
```

#### GTK# 3.0 program (no Glade)
```bash
mcs -pkg:gtk-sharp-3.0 myapp.cs
mono myapp.exe
```

#### GTK# 3.0 program using a Glade file
```bash
mcs -pkg:gtk-sharp-3.0 -pkg:glade-sharp-3.0 myapp.cs
mono myapp.exe
```
Make sure the `.glade` file referenced in code sits in the same folder (or update the path in `new Builder("...")`).

---

### 7. Minimal Working Examples

#### Hello World (console)
```csharp
using System;

class Hello {
    static void Main() {
        Console.WriteLine("Hello from Mono in WSL!");
    }
}
```
```bash
mcs hello.cs
mono hello.exe
```

#### Hello World (GTK# window, no Glade)
```csharp
using System;
using Gtk;

class GtkCheck {
    static void Main() {
        Application.Init();
        var win = new Window("Hello GTK#3");
        win.SetDefaultSize(300, 200);
        win.DeleteEvent += (o, args) => Application.Quit();

        var button = new Button("Click me");
        button.Clicked += (o, args) => Console.WriteLine("Clicked!");
        win.Add(button);

        win.ShowAll();
        Application.Run();
    }
}
```
```bash
mcs -pkg:gtk-sharp-3.0 gtkcheck.cs
mono gtkcheck.exe
```

#### Loading a UI built in Glade
```csharp
using System;
using Gtk;

class Program {
    static void Main() {
        Application.Init();
        var builder = new Builder("myui.glade");
        builder.Autoconnect(new Program());

        var win = (Window)builder.GetObject("window1");
        win.DeleteEvent += (o, args) => Application.Quit();
        win.ShowAll();

        Application.Run();
    }
}
```
```bash
mcs -pkg:gtk-sharp-3.0 -pkg:glade-sharp-3.0 program.cs
mono program.exe
```

---

### 8. Custom Output Names, Targets & Exporting DLLs

By default, `mcs file.cs` names the output after the source file (`file.exe`). You can control this with `-out:` and change what kind of binary is produced with `-target:`.

#### 8.1 Custom output name
```bash
mcs -pkg:gtk-sharp-3.0 Program.cs -out:MyCustomApp.exe
```
Run it the same way:
```bash
mono MyCustomApp.exe
```

#### 8.2 `-target` options
| Target | Produces | Notes |
|---|---|---|
| `exe` (default) | Console executable | Shows a console window when run on Windows |
| `winexe` | Windows GUI executable | No console window — correct choice for GTK#/Glade GUI apps distributed on Windows |
| `library` | `.dll` class library | No `Main` entry point required to run directly; used by other programs |
| `module` | `.netmodule` | Rarely used; for combining into a single assembly later |

Your example, building a windowed GUI executable with a custom name:
```bash
mcs -target:winexe -pkg:gtk-sharp-3.0 Program.cs -out:programwin.exe
```
This produces `programwin.exe`, which on Windows will run without popping up a console window alongside your GTK window.

#### 8.3 Exporting a DLL

If you want to package reusable code (helper classes, business logic, etc.) as a library instead of a standalone app:

```bash
mcs -target:library Program.cs -out:MyLibrary.dll
```

- No `Main()` method is required in a `library` target (though it's fine if one class in your project has one — it just won't be used as an entry point for the DLL itself).
- This creates `MyLibrary.dll`, a Mono/.NET assembly that other C# programs can reference.

#### 8.4 Using a DLL in another program

Suppose `MyLibrary.dll` contains:
```csharp
// MyLibrary.cs
namespace MyLib {
    public class Greeter {
        public string Greet(string name) => $"Hello, {name}!";
    }
}
```
Compiled with:
```bash
mcs -target:library MyLibrary.cs -out:MyLibrary.dll
```

Then in a separate consumer program:
```csharp
// ConsumerApp.cs
using System;
using MyLib;

class ConsumerApp {
    static void Main() {
        var greeter = new Greeter();
        Console.WriteLine(greeter.Greet("World"));
    }
}
```

Compile it while referencing the DLL with `-r:`:
```bash
mcs ConsumerApp.cs -r:MyLibrary.dll -out:ConsumerApp.exe
```

Run it (make sure `MyLibrary.dll` is in the same folder as `ConsumerApp.exe`, or on Mono's library search path):
```bash
mono ConsumerApp.exe
```

#### 8.5 Combining `-target:library` with GTK# packages
If your DLL itself uses GTK# types (e.g., a shared custom widget), include the package flag when building the library too:
```bash
mcs -target:library -pkg:gtk-sharp-3.0 MyGtkWidgets.cs -out:MyGtkWidgets.dll
```
And reference both the package and the DLL when compiling the consumer:
```bash
mcs -pkg:gtk-sharp-3.0 ConsumerApp.cs -r:MyGtkWidgets.dll -out:ConsumerApp.exe
```

#### 8.6 Quick reference
```bash
# Console exe, custom name
mcs Program.cs -out:myapp.exe

# Windowed (no console) exe, custom name
mcs -target:winexe -pkg:gtk-sharp-3.0 Program.cs -out:programwin.exe

# Class library (DLL)
mcs -target:library Library.cs -out:MyLibrary.dll

# Program referencing that DLL
mcs Consumer.cs -r:MyLibrary.dll -out:Consumer.exe
```

#### 8.7 Running the compiled .exe on Windows (outside WSL)

Compiling in WSL produces a `.exe` that targets the .NET/Mono runtime — it is **not** a native Windows binary, and it will not run on Windows by itself. Two things are needed:

**1. Install a runtime on the Windows machine**

Pick one:
- **Mono runtime for Windows** — install the [Mono for Windows](https://www.mono-project.com/download/stable/) package so `.exe` files built with `mcs` can run via the Mono runtime. After install, you can either double-click the `.exe` (Mono associates itself with `.exe` files) or run it explicitly:
```powershell
  mono programwin.exe
```
- **.NET Framework / .NET runtime** — for simple GTK#-free console apps, the .exe may also run under an installed .NET runtime, but GTK# apps specifically require Mono's GTK# libraries, so the Mono for Windows install is the reliable option.

**2. Copy the required DLLs alongside the .exe**

Your compiled `.exe` references managed DLLs at runtime (not just at compile time). Anything you referenced with `-r:` or `-pkg:` must physically travel with the `.exe`:

- Any custom library you built, e.g. `MyLibrary.dll` or `MyGtkWidgets.dll`
- GTK# binding assemblies your app depends on (typically already provided by installing Mono for Windows, since it bundles the GTK# runtime — but if you used a nonstandard package, copy its DLL too)
- Your `.glade` file, if the app loads one via `Builder("myui.glade")` — this isn't a DLL but is just as required; it must sit next to the `.exe` at the same relative path your code expects

Minimum folder layout to copy over to Windows:
programwin.exe
MyLibrary.dll (any custom DLLs you referenced with -r:)
myui.glade (if using Glade)

**Common pitfall:** a program that runs fine in WSL but fails on Windows with `FileNotFoundException` or `Could not load file or assembly` almost always means a referenced DLL (or the `.glade` file) wasn't copied over, or Mono for Windows isn't installed.

---

### 9. Common Errors & Fixes

| Error | Cause | Fix |
|---|---|---|
| `File does not contain a valid CIL image` | Ran `mono` on `.cs` source instead of compiled `.exe` | Compile first: `mcs file.cs`, then `mono file.exe` |
| `CS0246: type or namespace 'Gtk' could not be found` | Missing package reference during compile | Add `-pkg:gtk-sharp-3.0` to `mcs` command |
| `CS0103: name 'Console' does not exist` | Missing `using System;` | Add `using System;` at top of file |
| `Unable to locate package gtk-sharp2` | Package dropped in Ubuntu 24.04 | Use `gtk-sharp3` instead |
| `DllNotFoundException: libgdiplus` | Code uses `System.Drawing` without the lib installed | `sudo apt install libgdiplus` |
| `Could not load file or assembly 'MyLibrary.dll'` | DLL not in same folder / not found | Place DLL next to the `.exe`, or set `MONO_PATH` |
| `FileNotFoundException` / `Could not load file or assembly` (on Windows) | Runtime not installed, or referenced DLL/.glade not copied alongside the .exe | Install Mono for Windows; copy all `-r:` DLLs and `.glade` files into the same folder as the `.exe` |

---

### 10. Notes on Alternatives

GTK# is a legacy, lightly-maintained binding. For new projects, consider:
- **Avalonia UI** — modern, XAML-based, cross-platform, actively maintained
```bash
  sudo apt install dotnet-sdk-8.0
  dotnet new install Avalonia.Templates
  dotnet new avalonia.app -o MyApp
```

Use GTK#/Mono/Glade for legacy projects or learning; use Avalonia + `dotnet` for anything new and long-term.


Personal note:

Compile note:
    to compile the cs file
        mcs NAME_OF_THE_FILE.cs 
    for GTK appilcation
        mcs -pkg:gtk-sharp-3.0 NAME_OF_THE_FILE.cs
    custom output name / windowed exe
        mcs -target:winexe -pkg:gtk-sharp-3.0 NAME_OF_THE_FILE.cs -out:CUSTOM_NAME.exe
    export as DLL
        mcs -target:library NAME_OF_THE_FILE.cs -out:CUSTOM_NAME.dll
    use a DLL in another program
        mcs Consumer.cs -r:CUSTOM_NAME.dll -out:Consumer.exe



to run note:
    same for all cases
        mono NAME_OF_THE_FILE.exe

## OS

To test the application/setup across different Linux distributions, I used **Ventoy** to create a multiboot USB drive. Ventoy lets you copy multiple ISO files onto a single USB stick and choose which one to boot at startup, without needing to reformat or re-flash the drive for each OS.

### Workflow
1. Install Ventoy on the target USB drive.
2. Copy the desired `.iso` files directly onto the Ventoy partition (no extraction needed).
3. Boot from the USB, select the ISO from the Ventoy boot menu, and test the OS live or install it.

### OS images checked
- Fedora-Workstation-Live-44-1.7.x86_64
- lubuntu-26.04-desktop-amd64
- xubuntu-25.10-desktop-amd64

### OS images to check
- antiX-26_x64-full
- xubuntu-26.04-minimal-amd64


---

# EARS Application

Notes covering the GTK# port of the auscultation trainer: dynamic UI
generation from CSV, the notebook page structure, and the failure modes
that cost the most time.

---

## 11. Project Layout

Source lives in `program/`, build output in `program/bin/linux/`.
Runtime assets must sit **next to the .exe**, not next to the source.

```
program/
├── Program.cs
├── MainWindow.cs              root window + splash page
├── SettingWindow.cs           settings page controller
├── SoundWindow.cs             sound page controller
├── CaseDefinition.cs          CSV row model + loader
├── WavePlayer.cs              cross-platform wav playback
├── ArduinoConnection.cs
├── Language.cs
├── homepageallv3.glade
├── cases.csv
├── map/
├── sound/
└── bin/linux/                 ← everything above (minus .cs) copied here
    ├── program.exe
    ├── homepageallv3.glade
    ├── cases.csv
    ├── map/
    └── sound/
```

---

## 12. Single-Window Notebook Architecture

One `GtkWindow` (`Root`) holds one `GtkNotebook` (`RootNoteBook`) with
tabs hidden. Each "screen" is a notebook page; navigation is just
`RootNoteBook.CurrentPage = N`. No second window is ever created.

| Page | Index | Root widget | Controller |
|---|---|---|---|
| Splash | 0 | `SplashGrid` | `MainWindow` |
| Settings | 1 | `SettingGird` | `SettingsWindow` |
| Sound | 2 | `SoundBox` | `SoundWindow` |

`SettingsWindow` and `SoundWindow` are **not** `Gtk.Window` subclasses
despite the names — they're plain controller classes that receive the
shared `Builder` and bind their own widgets:

```csharp
public class SoundWindow
{
    [UI] private Label  ConditionNameLabel = null;
    [UI] private Grid   SoundButtonGrid    = null;
    [UI] private Button SoundBackButton    = null;

    public event EventHandler BackRequested;

    public SoundWindow(Builder builder)
    {
        builder.Autoconnect(this);
        ...
    }
}
```

Wired up in `MainWindow`'s private constructor:

```csharp
_soundWindow = new SoundWindow(builder);
_soundWindow.BackRequested += (s, e) => RootNoteBook.CurrentPage = PageSplash;
Option1Button.Clicked += OnNormalSessionClicked;
```

Controllers never touch the notebook directly — they raise
`BackRequested` and let `MainWindow` decide. Keeps navigation in one place.

**All three controllers share one `Builder`.** Each `Autoconnect` call
binds only the ids matching that class's `[UI]` fields, which is exactly
why ids must be unique across the entire file (§13).

---

## 13. Glade Rules That Bite

### IDs must be unique file-wide and valid C# identifiers

`Autoconnect` maps glade ids to field names by string match across the
**whole** builder, not per page. Two widgets sharing an id bind
unpredictably.

| Broken | Why | Fixed |
|---|---|---|
| `Condition Name` | Space — never matches a field name | `ConditionNameLabel` |
| `BackButto` | Typo | `SoundBackButton` |
| `BackButton` on two pages | Duplicate across pages | `SettingBackButton` + `SoundBackButton` |

A mismatch **fails silently** — the field stays `null` and you get a
`NullReferenceException` later, often several clicks away from the cause.
See §19 for the guard that catches this at startup.

Verify before running:

```bash
grep -o 'id="[^"]*"' homepageallv3.glade | sort | uniq -d
```

Any output is a duplicate id.

### Placeholders are design-time only

The hatched empty cells Glade shows in a `GtkGrid` save as
`<placeholder/>` and are **ignored at load time**. A button placed at
`left-attach=2` in a designer grid with two empty columns to its left
ends up at column 0 in the running app — this is the classic
"button jumps to the left" bug.

Never use placeholders for spacing or alignment. Use `halign` + `hexpand`.

### Right-aligning a button

Both properties are required:

```xml
<property name="halign">end</property>
<property name="hexpand">True</property>
```

`hexpand` makes the cell consume the full row width; `halign=end` parks
the button at the right edge of that cell. `halign` alone does nothing
when the cell is only as wide as the button.

In Glade: **Common** tab → *Horizontal Alignment* = `End`,
*Expand → Horizontal* = checked.

### Scrolling a grid needs a Viewport

`GtkGrid` doesn't implement `GtkScrollable`. Dropping one into a
`GtkScrolledWindow` requires an intermediate `GtkViewport` — Glade
inserts it automatically. Don't delete it.

```
SoundScroller     GtkScrolledWindow   hexpand + vexpand, packing expand=True
└─ GtkViewport                        (auto-added, required)
   └─ SoundButtonGrid  GtkGrid        empty, column-homogeneous=True
```

`GtkFlowBox` *is* scrollable and wraps children automatically, but the
`gtk-sharp3` binding on Ubuntu is 2.99.x and may not expose it. Check
before relying on it:

```bash
monop -r:/usr/lib/cli/gtk-sharp-3.0/gtk-sharp.dll Gtk.FlowBox
```

### Expand appears in two tabs

For a widget inside a `GtkBox`, **Common → Expand** sets the widget's own
`hexpand`/`vexpand`, while **Packing → Expand** sets the box child
property. They are different things and both usually need setting.

---

## 14. Generating Widgets From Code

Leave the container **empty** in Glade and fill it at runtime. Three
rules, all of which produce silent failures when broken:

```csharp
private void BuildButtons(IEnumerable<string> labels, Action<string> onPick)
{
    // 1. Remove AND destroy — Remove alone leaks the widget
    foreach (var child in SoundButtonGrid.Children)
    {
        SoundButtonGrid.Remove(child);
        child.Destroy();
    }

    int i = 0;
    foreach (string text in labels)
    {
        // 2. Capture the loop variable — otherwise every handler
        //    sees the final value
        string captured = text;

        var btn = new Button(captured);
        btn.Hexpand = true;
        btn.Clicked += (s, e) => onPick(captured);
        SoundButtonGrid.Attach(btn, i % Columns, i / Columns, 1, 1);
        i++;
    }

    // 3. Widgets created in code start HIDDEN. Without this the grid
    //    stays blank with no error of any kind.
    SoundButtonGrid.ShowAll();
}
```

`ShowAll()` on the container is the single most common cause of
"my buttons didn't appear" in GTK#.

### Drill-down navigation

All three modes (type → condition → play) use one grid and one render
method. State is a `List<string>` path; Back pops one level:

```csharp
private readonly List<string> _path = new List<string>();

private void Render()
{
    var matches = _allCases.Where(MatchesPath).ToList();

    var options = matches
        .Where(c => c.TreePath.Length > _path.Count)
        .Select(c => c.TreePath[_path.Count])
        .Distinct()
        .ToList();

    ConditionNameLabel.Text = _path.Count == 0
        ? "種別を選択"
        : string.Join(" : ", _path);

    BuildButtons(options, picked => { _path.Add(picked); /* leaf? play : Render(); */ });
}

private void OnBackClicked(object sender, EventArgs e)
{
    if (_path.Count > 0) { _path.RemoveAt(_path.Count - 1); Render(); }
    else { WavePlayer.Stop(); BackRequested(this, EventArgs.Empty); }
}
```

Tree depth varies per row (`Tree_Level3` is often empty), so
`CaseDefinition.TreePath` returns only the non-empty levels and the
drill-down adapts automatically.

---

## 15. Loading cases.csv

`CaseDefinition.LoadCasesFromCsv(path)` returns `List<CaseDefinition>`.
Column → property mapping:

| CSV column | Property |
|---|---|
| `Number` | `Number` |
| `Type` | `Type` (心音 / 呼吸音) — also drives `IsHeart` |
| `Category` | `CategoryJp` |
| `Subcategory` | `SubcategoryJp` |
| `Location` | `LocationJp` |
| `Tree_Level1..3` | `TreeLevel1..3` |
| `Image_File` | `MapFront` |
| `Sound_File` | `SoundPath` |
| `Image_Right` / `Image_Left` / `Image_Back` | `MapRight` / `MapLeft` / `MapBack` |

Four things the loader must get right:

**Encoding.** The file contains Japanese. Read with `Encoding.UTF8` and
BOM detection — the default ANSI codepage produces mojibake that only
shows up on the rendered buttons.

**Delimiter.** The file is TAB separated. Auto-detect so a re-export
from Excel as comma-separated doesn't break it:

```csharp
char sep = lines[0].Contains("\t") ? '\t' : ',';
```

**Header mapping.** Build `name → index` from row 0 rather than
hardcoding positions, so adding or reordering columns is safe.

**InvariantCulture on every numeric parse.** Same failure class as the
CSS scaling bug in §"scaling" — under `ja_JP`/`de_DE` the
culture-sensitive default silently misparses:

```csharp
int.TryParse(get(f, "Number"), NumberStyles.Integer,
             CultureInfo.InvariantCulture, out number)
```

The loader prints its result on every run:

```
Loaded 98 cases from /path/to/bin/linux/cases.csv
```

`Loaded 0 cases` points at the path or the delimiter, not the UI.

---

## 16. File Paths — AppFile()

**Never use bare relative paths.** `Path.GetFullPath("cases.csv")`
resolves against the *working directory*, so the app works when launched
from `program/` and silently loads nothing from anywhere else.

```csharp
public static string AppFile(string relative)
{
    string dir = Path.GetDirectoryName(
        System.Reflection.Assembly.GetExecutingAssembly().Location);
    return Path.Combine(dir, relative);
}
```

Use it for **everything**: the glade file, the CSV, maps, sounds.

```csharp
builder.AddFromFile(SoundWindow.AppFile("homepageallv3.glade"));
```

Mixing the two conventions is how you end up editing
`program/homepageallv3.glade` while the app reads
`bin/linux/homepageallv3.glade` — every fix appears to do nothing.

**Linux is case-sensitive.** `sound/snd200.wav` ≠ `sound/SND200.wav`,
which was fine on Windows and isn't now. Audit the CSV against the disk:

```bash
cut -f10 cases.csv | tail -n +2 | while read f; do
  [ -n "$f" ] && [ -f "$f" ] || echo "MISSING: $f"
done
```

---

## 17. Audio Playback

XAudio2/SharpDX from the WinForms build does **not** load under Mono on
Linux. `WavePlayer` shells out instead:

| Platform | Mechanism |
|---|---|
| Linux / WSL | `paplay`, falling back to `aplay` |
| Windows | `System.Media.SoundPlayer` |

Install both helpers — WSLg routes through PulseAudio, so `paplay` is
the one that works:

```bash
sudo apt install pulseaudio-utils alsa-utils
```

Verify outside the app before blaming the C#:

```bash
paplay bin/linux/sound/SND200.wav
echo $PULSE_SERVER          # empty under WSLg → wsl --shutdown and retry
```

**Format matters on Windows.** `SoundPlayer` handles PCM WAV only:

```bash
file sound/SND200.wav       # want: RIFF ... WAVE audio, Microsoft PCM
```

**`Stop()` is a no-op on Windows** — `SoundPlayer.Play()` returns no
handle. If stop-on-back is needed there, use `PlaySync()` on a
background thread or add NAudio.

---

## 18. Build & Run

Debug build (keeps the console so `Console.WriteLine` diagnostics show):

```bash
mcs -pkg:gtk-sharp-3.0 *.cs -out:./bin/linux/program.exe \
  && cp homepageallv3.glade cases.csv ./bin/linux/ \
  && cp -r map sound ./bin/linux/ \
  && mono ./bin/linux/program.exe
```

The `cp` steps are not optional — stale assets in `bin/linux/` are a
recurring source of phantom bugs (§16).

Re-copying every wav on each build gets slow. Once stable, symlink and
drop the `cp -r`:

```bash
ln -s ../../map   bin/linux/map
ln -s ../../sound bin/linux/sound
```

Release build (`-target:winexe` suppresses the console — don't use it
while debugging):

```bash
mcs -target:winexe -pkg:gtk-sharp-3.0 *.cs -out:./bin/windows/program.exe
```

---

## 19. Debugging Autoconnect Failures

An unbound `[UI]` field is `null` with no warning. Add an explicit guard
to every controller constructor so the failure names the widget at
startup:

```csharp
builder.Autoconnect(this);

if (ConditionNameLabel == null || SoundButtonGrid == null || SoundBackButton == null)
    throw new InvalidOperationException(
        "Glade id mismatch — ConditionNameLabel=" + (ConditionNameLabel != null) +
        " SoundButtonGrid=" + (SoundButtonGrid != null) +
        " SoundBackButton=" + (SoundBackButton != null));
```

Output looks like:

```
System.InvalidOperationException: Glade id mismatch —
  ConditionNameLabel=False SoundButtonGrid=True SoundBackButton=True
```

For classes with many fields, loop a dictionary instead:

```csharp
foreach (var pair in new Dictionary<string, object> {
    { "ComPortLabel", ComPortLabel }, { "ConnectButton", ConnectButton },
    { "SettingBackButton", SettingBackButton }, /* ... */ })
    if (pair.Value == null)
        throw new InvalidOperationException("Glade id not bound: " + pair.Key);
```

### Reading a GTK# stack trace

Exceptions inside signal handlers arrive wrapped:

```
System.Reflection.TargetInvocationException ---> System.NullReferenceException
  at SoundWindow.Render () [0x0007b]
```

Ignore the `GLib.SignalClosure` / `MarshalCallback` frames — they're
plumbing. The first frame naming **your** class is the real site, and
the `[0x...]` IL offset distinguishes lines within it.

### Common failures

| Symptom | Cause | Fix |
|---|---|---|
| NRE in controller ctor | `[UI]` field id mismatch | §19 guard, then fix the glade id |
| NRE on first click, no CSV log line | Controller never constructed | Assign it in the **private** `MainWindow(Builder)` ctor |
| Buttons don't appear, no error | Missing `ShowAll()` | Call it on the container after `Attach` |
| Every button does the same thing | Closure over loop variable | `string captured = text;` |
| `Loaded 0 cases` | Wrong path or delimiter | Use `AppFile()`; check tab vs comma |
| Button sits left despite `halign=end` | Relying on grid placeholders | Add `hexpand=True` |
| Fix appears to do nothing | Editing a different copy of the glade | Use `AppFile()` + `cp` on build |
| Japanese renders as garbage | Wrong encoding | `File.ReadAllLines(path, Encoding.UTF8)` |
| GTK warning about scrolling | Grid directly in ScrolledWindow | Keep the `GtkViewport` |

---

## 20. Windows Deployment Differences

The C#, the glade file, and the CSV are **identical**. Four differences:

1. **Audio** — see §17. PCM-only, and `Stop()` doesn't work.
2. **Build target** — `-target:winexe` for no console window.
3. **Runtime** — Mono for Windows must be installed on the target
   machine; it bundles the GTK# runtime.
4. **Serial ports** — `ArduinoConnection` enumerates `COM*` rather than
   `/dev/ttyUSB*` / `/dev/ttyACM*`. Mono's Linux `SerialPort.GetPortNames()`
   has historically missed devices; verify against `ls /dev/tty*`.

Ship this folder:

```
program.exe
homepageallv3.glade
cases.csv
map/
sound/
```

Not problems, for the record: forward slashes in CSV paths work fine on
Windows, and Windows' case-insensitivity means anything working on Linux
also works there — never the reverse. Develop on Linux and Windows
comes free.