Popup で ComboBox を使うと フリーズ する。

今日仕事で嵌ったので調べてみた。
Silverlight の Popup で表示している ComboBox をクリックしたら、フリーズしました。
MSDN Popup クラス のサンプルに ComboBox 追加して試しても発生したので間違いないと思う。

以下ミニマムコード

Page.xaml

<UserControl x:Class="SilverlightApplication3.Page"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  Width="400" Height="300">
  <Grid x:Name="LayoutRoot" Background="Yellow">
    <Button Content="test" Click="Button_Click" Width="80" Height="30" />
  </Grid>
</UserControl>

Page.cs

using System.Windows;
using System.Windows.Controls;

namespace SilverlightApplication3
{
  public partial class Page : UserControl
  {
    public Page()
    {
      InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      SilverlightControl1 s = new SilverlightControl1();
      s.ShowDialog();
    }
  }
}

SilverlightControl1.xaml

<UserControl x:Class="SilverlightApplication3.SilverlightControl1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  Width="200" Height="100">
  <Grid x:Name="LayoutRoot" Background="Aqua">
    <StackPanel Orientation="Vertical">
      <TextBlock Text="test"/>
      <ComboBox>
        <ComboBoxItem Content="Test1" IsSelected="True"/>
        <ComboBoxItem Content="Test2"/>
        <ComboBoxItem Content="Test3"/>
      </ComboBox>
      <Button Content="Close" Click="Button_Click"/>
    </StackPanel>
  </Grid>
</UserControl>

SilverlightControl1.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace SilverlightApplication3
{
  public partial class SilverlightControl1 : UserControl
  {
    Popup _popup = new Popup();
    public SilverlightControl1()
    {
      InitializeComponent();

      this._popup.Child = this;
    }

    public void ShowDialog()
    {
      if (this._popup.IsOpen)
      {
        return;
      }
      this._popup.IsOpen = true;
    }

    public void CloseDialog()
    {
      this._popup.IsOpen = false;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      this.CloseDialog();
    }
  }
}

調べたら、こんなサイトが出てきた。MSDN Silverlight Dev Center(相変わらず英語が読めない)
Popup を VisualTree に追加したら、回避出来るらしい。(そう読みとれた。間違ってるかも)


VisualTree ってなんだ?良く分からないので、「Silverlight VisualTree」で調べてみてもあんまり出てこない。
Silverlight ビジュアル ツリー」 で MSDN Silverlight オブジェクト ツリー が引っ掛かった。
どうも、表示される物(コントロール)の階層構造?みたいなのらしい(間違ってたら、指摘して下さい)


とりあえず、親画面の LayoutRoot に Popup を 追加してたら、回避出来た。
以下修正後ソース

Page.xaml は変更なし
Page.cs

using System.Windows;
using System.Windows.Controls;

namespace SilverlightApplication3
{
  public partial class Page : UserControl
  {
    public Page()
    {
      InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      SilverlightControl1 s = new SilverlightControl1(this.LayoutRoot); // Popup を追加するため、LayoutRoot(Grid)を渡す。
      s.ShowDialog();
    }
  }
}

SilverlightControl1.xaml

<UserControl x:Class="SilverlightApplication3.SilverlightControl1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  Width="200" Height="100">
  <Grid Background="Aqua"> <!-- 複数追加する事を考えて、Name を指定しない。 -->
    <StackPanel Orientation="Vertical">
      <TextBlock Text="test"/>
      <ComboBox>
        <ComboBoxItem Content="Test1" IsSelected="True"/>
        <ComboBoxItem Content="Test2"/>
        <ComboBoxItem Content="Test3"/>
      </ComboBox>
      <Button Content="Close" Click="Button_Click"/>
    </StackPanel>
  </Grid>
</UserControl>

SiverlightControl1.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace SilverlightApplication3
{
  public partial class SilverlightControl1 : UserControl
  {
    Popup _popup = new Popup();
    Panel _parent; // Popup を追加するコントロール(ここでは、Page.LayoutRoot が渡ってくる)
    public SilverlightControl1(Panel parent)
    {
      if (parent == null)
      {
        throw new ArgumentNullException("parent");
      }
      InitializeComponent();
      this._popup.Child = this;
      this._parent = parent;
    }

    public void ShowDialog()
    {
      if (this._popup.IsOpen)
      {
        return;
      }
      this._parent.Children.Add(_popup); // 表示する前に、Popup を 追加する。
      this._popup.IsOpen = true;
    }

    public void CloseDialog()
    {
      if (!this._popup.IsOpen)
      {
        return;
      }
      this._popup.IsOpen = false;
      this._parent.Children.Remove(this._popup); // 閉じたら、Popup を 削除する。
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      this.CloseDialog();
    }
  }
}

これで、回避出来たよ。


注意点!!

  • Popup で表示するコントロールに名前を付けていると、親で表示しているコントロールと名前が被った場合エラーになる。

(多分、ツリーには同じ名前がいたらダメ?)

  • Popup は、モーダル では無いので続けて呼び出される可能性がある。