Windows向けの.NET 9 + .NET MAUIでEditorのテキストをマウス選択すると、選択ハイライトが消えずに残り続けることがあります。デバッグ実行では再現しないのに、Releaseのexe実行だけで起きるケースもあり、切り分けが難しいのが厄介です。この記事では原因のポイントと、SelectionChangedで背景を再描画する回避策、アップデートによる恒久対応をまとめます。
発生する症状と再現条件(Windows版 .NET MAUI Editor)
今回の現象は「選択状態が変わっているのに、見た目のハイライトだけが残像として残る」タイプの不具合です。まずは、読者の環境が同じ症状かどうかを素早く照合できるように、特徴を整理します。
| 項目 | 内容 |
|---|---|
| 環境 | .NET 9 + .NET MAUI(Windows) |
| 対象コントロール | Editor |
| 主な現象 | マウス左クリックで文字列を選択すると、選択ハイライトが解除されず残り続ける |
| 二次症状 | 別の箇所を選択しても、古いハイライトが消えず重なって表示される |
| 再現条件 | Releaseビルドのexe実行でのみ発生(Visual Studioのデバッグ実行では再現しない) |
| レイアウト例 | RadBorder + ScrollView + Editor のように、境界/スクロールを絡めた配置で目立ちやすい |
質問で使われているXAML構成は、ログ表示や長文編集のUIでよくあるパターンです。
<telerik:RadBorder ...>
<ScrollView x:Name="scrollView">
<Editor x:Name="editor"
AutoSize="TextChanges"
IsSpellCheckEnabled="false"
IsTextPredictionEnabled="false"
Text="{Binding LogText, Mode=TwoWay}" />
</ScrollView>
</telerik:RadBorder>
ポイントは、文字列選択そのものは内部的に変化しているのに、見た目のハイライト描画が更新されず、以前の選択範囲の背景が画面に残ってしまう点です。いわゆる「描画の取りこぼし」「再描画の欠落」に近い挙動です。
原因の概要:Editor(Windows)側の再描画が追いつかず、選択背景が残像化する
.NET MAUIのEditorは、WindowsではWinUIのネイティブコントロールをベースに動作します。通常、選択範囲が変わると、以前の選択範囲の背景が消えて、新しい選択範囲だけがハイライトされます。
しかしこの不具合が出ている状態では、選択変更時に本来行われるべき背景(選択ハイライトを含む領域)の再描画が不完全になり、以前の選択範囲の背景だけが画面に残ってしまいます。その結果、次の選択をすると新しい選択範囲のハイライトが上書きではなく「追加で塗られたように」見え、ハイライトが重なって表示されます。
そして厄介なのが「Debug実行では再現しない」点です。これは一般に、デバッグ実行では内部の描画タイミングやレイアウト更新、フレームレート、UIスレッドの負荷、デバッグ用フックなどが変わり、結果として偶然うまく再描画されて症状が隠れることがあるためです。Releaseのexe実行では最適化の影響でタイミングが変わり、描画の取りこぼしが表面化しやすくなります。
最初にやるべき切り分け:本当に「選択ハイライト残留」なのか確認する
対処コードを入れる前に、同じ見え方でも原因が異なるケース(たとえば独自描画、スクロールのクリッピング、テーマ/アクセント色の問題など)を排除しておくと、遠回りを減らせます。
| チェック | 狙い | やり方の例 |
|---|---|---|
| Release exeで再現するか | 条件が一致しているか | DebugではなくReleaseでビルドし、exeを直接起動して確認 |
| ScrollViewを外す | スクロール/クリッピングの影響の切り分け | Editor単体配置にして再現するか見る |
| RadBorder等の装飾を外す | 境界線・角丸・陰影が描画更新を邪魔していないか | 通常のBorder/Frame/何も無しで確認 |
| AutoSizeを固定にする | サイズ変動が再描画に影響していないか | AutoSizeを外しHeightRequestを固定して比較 |
| ログ用途ならReadOnlyを検討 | 編集系イベントが絡むかの切り分け | EditorのIsReadOnlyをTrueにして挙動が変わるか確認 |
この切り分けで「Release exeだけで、選択を変えるほどハイライトが増殖する」なら、今回の対策が刺さる可能性が高いです。
回避策:SelectionChangedのタイミングで背景を強制更新する(EditorHandler拡張)
アプリ側でWindows用のハンドラを拡張し、SelectionChangedが発生した瞬間に背景の更新(再描画)を促すことで、残留した選択ハイライトを消して正常な見た目に戻します。
質問で提示されている回避策は、まさにこの方針です。ポイントは以下の通りです。
- Windowsビルドのときだけ有効にする(#if WINDOWS)
- Editorのネイティブビューに対してSelectionChangedを購読する
- イベント発火のたびにUpdateBackgroundを呼んで背景を再描画させる
実装例:MauiProgram.csでMapperに追記する(基本形)
起動時に実行される箇所(多くの場合はMauiProgram.cs)に追記します。WordPressに貼り付けやすいよう、必要になりやすいusingも含めて例を載せます。
#if WINDOWS
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using Microsoft.UI.Xaml;
#endif
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
// ここより上は従来の設定...
#if WINDOWS
EditorHandler.Mapper.AppendToMapping("SelectionChanged", (handler, view) =>
{
handler.PlatformView.SelectionChanged +=
new RoutedEventHandler((sender, e) =>
{
// 選択状態が変わったタイミングで背景を描き直す
handler.PlatformView.UpdateBackground(view);
});
});
#endif
return builder.Build();
}
}
この形は「最小の変更で効かせる」ことに向いています。Releaseビルド → exe実行で再テストし、古いハイライトが残らずに選択表示が追従するか確認してください。
実装の意図:なぜUpdateBackgroundで直るのか
症状の本質は「以前の選択領域の背景が消されない(塗り直されない)」なので、SelectionChangedのたびに背景更新を強制してやれば、残像として残った領域も含めて描画が更新されます。結果として「見た目のハイライトが増殖する」状態を断ち切れます。
注意点:イベント購読の二重登録を避ける(長時間運用・画面遷移が多いアプリ向け)
上の基本形は分かりやすい一方、画面遷移でEditorが何度も生成/破棄されるアプリでは、イベント購読の管理を丁寧にしておくと安心です。実際にはハンドラ単位で生成されるため致命的になりにくいものの、運用で「謎の多重発火」を避けたい場合は、より堅牢な実装に寄せるのが無難です。
より堅牢な実装:カスタムEditorHandlerでConnect/Disconnect時に購読を管理する
応急処置をプロジェクトに残すなら、Mapper追記よりも「ハンドラをサブクラス化して、接続時に購読・切断時に購読解除」する形が保守しやすいです。理由はシンプルで、イベント購読のライフサイクルが明確になるからです。
カスタムハンドラの例(Windowsのみ)
#if WINDOWS
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace YourApp.Platforms.Windows;
public class FixedEditorHandler : EditorHandler
{
protected override void ConnectHandler(TextBox platformView)
{
base.ConnectHandler(platformView);
platformView.SelectionChanged += OnSelectionChanged;
}
protected override void DisconnectHandler(TextBox platformView)
{
platformView.SelectionChanged -= OnSelectionChanged;
base.DisconnectHandler(platformView);
}
private void OnSelectionChanged(object sender, RoutedEventArgs e)
{
// VirtualView(= MAUI側のEditor)を使って背景更新
if (sender is TextBox textBox && VirtualView is IView view)
{
textBox.UpdateBackground(view);
}
}
}
#endif
MauiProgram.csでカスタムハンドラを登録する
using Microsoft.Maui.Handlers;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.ConfigureMauiHandlers(handlers =>
{
#if WINDOWS
handlers.AddHandler();
#endif
});
return builder.Build();
}
}
この形なら、画面を閉じたときに購読解除され、長時間稼働や画面の出入りが多いアプリでも安心です。「いったん直したが、後から不具合の温床になるのが怖い」という現場あるあるにも対応できます。
| 実装パターン | メリット | デメリット | おすすめ用途 |
|---|---|---|---|
| Mapper.AppendToMapping | 差分が小さく、導入が速い | ライフサイクルが見えにくい場合がある | まず直してリリースしたい応急処置 |
| カスタムEditorHandler | 購読/解除が明確で保守しやすい | ファイル追加・登録が必要 | 長期運用、画面遷移が多いアプリ |
恒久対応:.NET 9のServicing Release(SR)での修正を取り込む
この問題は.NET 9のSR(Servicing Release)で修正されたとされており、可能であればSDKとランタイムをアップデートして根本解決を狙うのが理想です。質問の内容では「.NET 9 SR4以降」での改善が示されています。
実務的には次の方針で考えると、無理なく移行できます。
- まずは回避策(ハンドラ修正)でRelease exeの見た目崩れを止血する
- 次に、開発環境のSDKと、配布先に必要なランタイムを最新の安定版へ更新する
- 更新後、回避策が不要になったことを確認して、コードを簡素化(削除)する
開発環境の確認:SDKバージョンを把握する
まず、開発PCで使用しているSDKが何かを確認します。コマンドプロンプト(またはPowerShell)で以下を実行します。
dotnet --info
dotnet --list-sdks
複数のSDKが入っている場合、プロジェクトがどのSDKでビルドされるかはglobal.jsonやVisual Studioの設定にも左右されます。意図せず古いSDKでReleaseを作っていると「修正が入っているはずなのに直らない」状況になりやすいので注意してください。
配布先(実行環境)の確認:Framework-dependentかSelf-containedか
Release exeの配布方法によって「更新すべき対象」が変わります。
| 配布形態 | 特徴 | 更新の考え方 |
|---|---|---|
| Framework-dependent | 実行先PCに.NETランタイムが必要 | 実行先の.NETランタイムを更新することで改善する可能性がある |
| Self-contained | アプリにランタイムを同梱する | 開発環境を更新し、再ビルドして配布し直す必要がある |
「デバッグでは再現しないが、配布したexeだけで起きる」という場合、実行先PCのランタイム差分も疑うべきポイントです。社内配布や複数端末での動作が絡む場合は、テスター端末のランタイムも含めて揃えるとトラブルが減ります。
アップデート後の方針:回避策を残すか、削除するか
アップデートで直るなら、回避策は削除してコードをシンプルに戻すのが理想です。ただし実務では「すべての端末が同時に更新できない」「一部の顧客環境では古いランタイムが残る」こともあります。その場合は次のように段階的に運用すると安全です。
- 回避策は#if WINDOWSで囲み、影響範囲を限定する
- さらに必要なら、アプリ設定(フラグ)でON/OFFできるようにする
- 更新が行き渡ったら、回避策を削除してメンテ負担を下げる
実装上の補足:ScrollView + Editor構成で気をつけたい点
質問のXAMLは、ログのように「長文を自動で伸ばしてScrollViewでスクロールする」用途でよく採用されます。ただ、Editorはプラットフォームによって内部スクロールの挙動が異なり、外側のScrollViewと組み合わせると描画や入力処理が複雑になりがちです。
今回の不具合を踏まえると、次の観点も合わせて見直すと安定します。
- ログ表示が目的なら、Editorではなく「読み取り専用表示」に寄せる(例:編集不可にする、別コントロールの検討)
- AutoSizeで伸び続けるUIは、長文になるほどレイアウト更新が重くなるため、一定以上は仮想化できるUI(CollectionViewなど)への移行も選択肢
- それでもEditorが必要なら、今回のように選択変更時の再描画を補助して見た目の破綻を防ぐ
「直ったら終わり」ではなく、ログ表示や長文表示という用途に対して、UIの責務が合っているかも合わせて考えると、将来的な不具合の芽を減らせます。
よくある質問
Releaseだけで起きるのは、ビルド設定(トリミング等)の影響ですか?
今回の症状は見た目が「描画が残る」タイプなので、根っこは再描画タイミング/無効化領域の問題であることが多いです。とはいえReleaseでは最適化やタイミングが変わるため、結果的に表面化します。まずは本記事の回避策で描画更新を補助し、同時にSDK/ランタイム更新も検討するのが現実的です。
UpdateBackgroundを呼ぶのは安全ですか?
SelectionChangedは頻繁に連打されるイベントではありますが、通常の操作範囲であれば許容されることが多いです。大量テキストで選択ドラッグを多用する運用だと呼び出し回数が増えますが、根本的に「残像が残って操作不能に見える」状況を避ける価値が高いケースがほとんどです。
この回避策はWindows以外に影響しますか?
コードを#if WINDOWSで囲っているため、iOS/Android/macOSなど他プラットフォームには影響しません。マルチターゲットのMAUIプロジェクトでも安心して入れられるのが利点です。
まとめ:応急処置→環境更新の二段構えが現場では強い
Windows版の.NET MAUI Editorで「選択ハイライトが消えずに残る」「重なって表示される」不具合は、Releaseのexe実行でのみ表面化することがあり、発見が遅れがちです。まずはSelectionChangedで背景更新を強制し、見た目の破綻を止めるのが最短ルートです。
- 症状の本質は、選択変更時に背景の再描画が正しく走らず残像が残ること
- 回避策は、Windows向けにEditorHandlerを拡張し、SelectionChangedでUpdateBackgroundを呼ぶ
- 長期運用なら、Mapper追記よりカスタムEditorHandlerで購読/解除を管理すると安全
- 可能なら、.NET 9のSR(Servicing Release)を取り込み、恒久的な修正を利用する
「いま配布しているRelease exeを安定させる」ことと、「将来的に回避コードを消してシンプルに保つ」ことを両立しやすいので、まずは止血、その後にアップデートという順で進めるのがおすすめです。

コメント