.NET Frameworkフォームのメニュー項目にラジオボタンをふるまわさせる(LINQ使用)


はじめに

フォームのメニューで、ラジオボタン(オプションボタン)的な「排他的チェックマーク付け機能」を簡単に実装をしてみます。チェックボックス的な仕組みは標準で搭載されているのですが...

コードと説明

.NET Framework 4.5.2 で、「Windows フォームアプリケーション」プロジェクトを作成しています。

Form1.cs
using System;
using System.Linq;
using System.Windows.Forms;

namespace SampleApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            //  初期設定(チェックマークをつけておきます)
            //  フォームデザイナでプロパティ指定してもよいです。
            ToolStripMenuItemViewFontSizeMedium.CheckState = CheckState.Indeterminate;
            ToolStripMenuItemViewFontNonBold.CheckState = CheckState.Indeterminate;
        }

        private void ToolStripMenuItemViewFontSizeLarge_Click(object sender, EventArgs e)
        {
            CheckToolStripMenuItemAsRadioItem(sender);
        }

        private void ToolStripMenuItemViewFontSizeMedium_Click(object sender, EventArgs e)
        {
            CheckToolStripMenuItemAsRadioItem(sender);
        }

        private void ToolStripMenuItemViewFontSizeSmall_Click(object sender, EventArgs e)
        {
            CheckToolStripMenuItemAsRadioItem(sender);
        }

        private void ToolStripMenuItemViewFontSizeExtraSmall_Click(object sender, EventArgs e)
        {
            CheckToolStripMenuItemAsRadioItem(sender);
        }

        /// <summary>
        /// クリックされたメニュー項目を、同位置にあるメニュー項目をグループとするのラジオボタンとしてチェックします。
        /// </summary>
        /// <param name="clickedItem"></param>
        private void CheckToolStripMenuItemAsRadioItem(object clickedItem)
        {
            //  選択されたメニュー項目
            var thisItem = clickedItem as System.Windows.Forms.ToolStripMenuItem;
            if (thisItem == null)
                return;

            //  その親項目
            var containerItem = thisItem.GetCurrentParent();

            //  (Linqを使った例)
            //  その子項目をToolStripMenuItem型のみ(つまりセパレータなどは除外して)列挙を行い
            //  選択された項目であればIntermediate、それ以外はUncheckedを、CheckStateプロパティに設定します。

            //  2018-10-20 All()使用でリソース消費を抑えています - albireoさんのコメントより
            containerItem.Items.OfType<System.Windows.Forms.ToolStripMenuItem>()
                .All(f => { f.CheckState = (f == thisItem) ? CheckState.Indeterminate : CheckState.Unchecked; return true; });
            //  もとはこちら
            //  containerItem.Items.OfType<System.Windows.Forms.ToolStripMenuItem>().ToList()
            //    .ForEach(f => f.CheckState = (f == thisItem) ? CheckState.Indeterminate : CheckState.Unchecked);
        }
    }
}

CheckToolStripMenuItemAsRadioItem という名のメソッドに集約して、メニューにラジオボタン的ふるまいをさせています。コメントなどを除けば、実質1行にまとめられる内容です。

自分の親の子たちは自分と兄弟姉妹、ということで、「親の子たち」をラジオボタングループのメンバーとみなし、LINQ を使って列挙してチェックマークを設定しています。

対象のメニュー項目をクリックされたイベントハンドラから、前述のこのメソッドを呼び出しています。
クリックされたメニュー項目の「親の子たち」から、 Enumerable.OfType() メソッドでメニュー項目である ToolStripMenuItem 型のもののみ(つまりセパレータなどは除かれます)を列挙し、クリックされた項目であれば CheckState.Intermediate 、そうでなければ CheckState.Unchecked を CheckState プロパティに設定しています。
列挙後の各要素(=メニュー項目)へのプロパティ設定操作は、LINQ の Allメソッドによる評価式内で行わせています(albireoさんのコメントにより改善してみました)

おわりに

LINQ は本当に便利ですね!
あと、ToolStripMenuItem.CheckState プロパティに CheckState.Intermediate を指定して表示される丸点は本来ラジオボタンの選択マークではないのですが、似ているので使ってみています。従来からのWindowsフォームのメニュー項目向けには、CheckMenuRadioItem という、純正ラジオマークを付与する Win32API が用意されているのですよね。。