概要
開閉するコントロールなら何でもいいですが、ここでは複数のExpanderがあるとします。
これらを同時に1つしか開かせないようにするとを実現します。
下のような感じですね。
そんなにわらわら並べないでTabでいいじゃんとかNavigation Railでいいじゃんとかは…まあそうですね。
前提
ReactivePropertyを使います。
Bindingを簡単にするというのもありますが、Pairwiseがあるのでこれを利用します。
実装
IReactivePrpoerty<bool>の配列等を受け取ってストリームに変換し、
trueの数が2以上になったら古い方のtrueをfalseに戻してやるような購読を行います。
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は見栄えのためだけです。
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しているものを配列に放り込んで、先ほど作った拡張メソッドを使います。
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のグループ化を真似して添付プロパティにでもした方が楽かもしれない…
ソースコード