複数あるExpanderを同時に1つしか展開させない

# 概要

開閉するコントロールなら何でもいいですが、ここでは複数のExpanderがあるとします。
これらを同時に1つしか開かせないようにするとを実現します。
下のような感じですね。
![allowonlyonetrue.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1135585/224d339c-6b2e-20d5-ab88-126274c9f8f1.gif)
そんなにわらわら並べないでTabでいいじゃんとかNavigation Railでいいじゃんとかは…まあそうですね。

# 前提

ReactivePropertyを使います。
Bindingを簡単にするというのもありますが、Pairwiseがあるのでこれを利用します。

# 実装

IReactivePrpoerty\<bool\>の配列等を受け取ってストリームに変換し、
trueの数が2以上になったら古い方のtrueをfalseに戻してやるような購読を行います。

```csharp:Ext.cs
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;

namespace AllowOnlyOneOpen.Extensions
{
    public static class Ext
    {
        public static IDisposable AllowOnlyOneTrue(this IEnumerable<IReactiveProperty<bool>> source)
            => source
            .CombineLatest()
            .Pairwise()
            .Where(x => x.NewItem.SkipWhile(n => !n).Skip(1).Any(n => n)) // コントロールの個数を考えるとCount()を素直に使ってよさそうですね
            .Subscribe(p => p.OldItem
                .Select((v, i) => (v, i))
                .Where(x => x.v)
                .Select(x => source.ElementAt(x.i).Value = false)
                .ToList());
    }
}
```

# View

ただExpanderが4つ並んでいるだけです。MaterialDesignThemeは見栄えのためだけです。

```xml:MainWindow.xml
<Window
    x:Class="AllowOnlyOneOpen.Views.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
    xmlns:vm="clr-namespace:AllowOnlyOneOpen.ViewModels"
    Title="MainWindow"
    Width="400"
    Height="440"
    d:DataContext="{d:DesignInstance Type=vm:MainWindowViewModel,
                                     IsDesignTimeCreatable=True}"
    Foreground="{StaticResource MaterialDesignDarkForeground}"
    Background="{StaticResource MaterialDesignDarkBackground}"
    mc:Ignorable="d">
    <StackPanel>
        <Expander Header="A" IsExpanded="{Binding AIsExpanded.Value, Mode=TwoWay}">
            <Rectangle Height="100" Fill="Cyan" />
        </Expander>
        <Expander Header="B" IsExpanded="{Binding BIsExpanded.Value, Mode=TwoWay}">
            <Rectangle Height="100" Fill="Gray" />
        </Expander>
        <Expander Header="C" IsExpanded="{Binding CIsExpanded.Value, Mode=TwoWay}">
            <Rectangle Height="100" Fill="LightGreen" />
        </Expander>
        <Expander Header="D" IsExpanded="{Binding DIsExpanded.Value, Mode=TwoWay}">
            <Rectangle Height="100" Fill="Violet" />
        </Expander>
    </StackPanel>
</Window>
```

# ViewModel

ViewにBindしているものを配列に放り込んで、先ほど作った拡張メソッドを使います。

```csharp:MainWindowViewModel.cs
using AllowOnlyOneOpen.Extensions;
using Reactive.Bindings;

namespace AllowOnlyOneOpen.ViewModels
{
    public class MainWindowViewModel : ViewModelBase
    {
        public ReactivePropertySlim<bool> AIsExpanded { get; } = new();
        public ReactivePropertySlim<bool> BIsExpanded { get; } = new();
        public ReactivePropertySlim<bool> CIsExpanded { get; } = new();
        public ReactivePropertySlim<bool> DIsExpanded { get; } = new();

        public MainWindowViewModel()
        {
            var group = new [] { AIsExpanded, BIsExpanded, CIsExpanded, DIsExpanded };
            group.AllowOnlyOneTrue();
        }
    }
}

```

# 雑感

RadioButtonのグループ化を真似して添付プロパティにでもした方が楽かもしれない…

# ソースコード

https://github.com/ryuix/qiitaposts/tree/main/AllowOnlyOneOpen