たぷつきません

おなかがでてきた。もうたぷついてるやん。

Popupのアニメーションがfadeでも、Buttonで正しくPopupの開閉を制御する。

 Popupが自動的に閉じられるようにするためには、StaysOpenをFalseにしないとならないが、Popupを開くボタンを用意した際に不都合が生じる。というのも、トップレベルのメニューアイテムのように一度クリックしてPopupを開き、もう一度クリックでPopupを閉じるということが単純にはできない。クリックするたびに一瞬消えて常に毎回開くことになる。
 これを制御するには、PopupのClosedイベントで、Mouse.Capturedがボタンの場合は、次のクリック時には開かない(Popup.IsOpen を trueにしない)という方法を取ればよい。

<Button Name="button1" Content="Popup開閉" Click="button1_Click"/>
<Popup Name="popup" Closed="popup_Closed" StaysOpen="False"
       PlacementTarget="{Binding ElementName=button1}" Placement="Bottom">
...
</Popup>
private bool _closedByButton;

private void button1_Click(object sender, RoutedEventArgs e)
{
    if (_closedByButton)
    {
        _closedByButton = false;
        Console.WriteLine("cancel open because by _closedByButton = true");
        return;
    }
    popup.IsOpen = true;
}

private void popup_Closed(object sender, EventArgs e)
{
    _closedByButton = (Mouse.Captured == button1);
}

 しかし、これはPopupのPopupAnimationがFade以外の話。Fade以外ではPopupが閉じる際にアニメーションが行われず即座にClosedが実行されるので、ボタンがマウスをキャプチャしている状態になっている(本当は、ScrollでもSlideでも閉じるアニメーションして欲しいのだけど…)。しかしFadeの場合はフェードアウトするアニメーションが終わって初めてClosedが実行されいるので、すでに次のボタンクリックは実行されているわ、すでにキャプチャは外れているわで、捕らえられない。
 というわけで、Closedが終わっていない場合はPopupを開かないという制御を加えると良い。合わせると以下になる。

<Button Name="button1" Content="Popup開閉" Click="button1_Click"/>
<Popup Name="popup" Closed="popup_Closed" StaysOpen="False"
       PopupAnimation="fade" PlacementTarget="{Binding ElementName=button1}" Placement="Bottom">
...
</Popup>
private bool _closedByButton;
private bool _closedCalled = true;

private void button1_Click(object sender, RoutedEventArgs e)
{
    if (_closedByButton)
    {
        _closedByButton = false;
    } else if (_closedCalled)
    {
        popup.IsOpen = true;
        _closedCalled = false;
    }
}

private void popup_Closed(object sender, EventArgs e)
{
    _closedByButton = (Mouse.Captured == button1);
    _closedCalled = true;
}

 これでPopupAnimationがどうであろうとボタンによる開閉制御が正しく行えるようになる。まあフラグ制御だらけで気持ち悪いけど。PopupだけでなくContextMenuをボタンで制御したい場合にも応用できるはず。
 Vistaで確認しただけなので、OSによってタイミング崩れないかは後で確認しなければ。

応用編

 ButtonをToggleButtonにすると_closedCalledフラグを使わずに、ToggleButtonの押し込み状態で制御できるので、こっちの方が良いかも。

private bool _closedByButton;

private void button1_Click(object sender, RoutedEventArgs e)
{
    if (_closedByButton)
    {
        _closedByButton = false;
        button1.IsChecked = false;
    } else if (button1.IsChecked == true)
    {
        popup.IsOpen = true;
    }
}

private void popup_Closed(object sender, EventArgs e)
{
    _closedByButton = (Mouse.Captured == button1);
    button1.IsChecked = false;
}