views:

2088

answers:

3

I'm having a go at making a Silverlight2 UserControl to emulate the basic functionality of a WinForms MessageBox. I was following Shawn Wildermuth and John Papa's examples.

Compiles fine, I add the user control to my app with a button to show the popup. This works - popup appears, you can click a button to close it and it closes. You can do this multiple times.

The problem appears if the UserControl that contains the "MessageBox" is closed and a new one created. If you try and open the popup on the new instance, it throws an exception:

Unhandled Error in Silverlight 2 Application
Code: 4004
Category: ManagedRuntimeError
Message: System.ArgumentException: Value does not fall within the expected range.
  at MS.Internal.XcpImports.CheckHResult(UInt32 hr)
  at MS.Internal.XcpImports.FrameworkElement_MeasureOverride(FrameworkElement element, Size availableSize)
  at System.Windows.FrameworkElement.MeasureOverride(Size availableSize)
  ...
  (stack trace doesnt go as far as any of my code)

The code for the control is at the bottom of the post. If you want to run it, I've made a testbench project to demonstrate the problem. The testbench has on-screen instructions to tell you how to reproduce the problem.

This has been driving me nuts, so any help or pointers would be greatly appreciated.

Here is the code for the PopupMessage user control:

<UserControl x:Class="SilverlightPopupControlTest.PopupMessage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows" 
    >
    <Grid x:Name="LayoutRoot">

        <vsm:VisualStateManager.VisualStateGroups>
         <vsm:VisualStateGroup x:Name="PopupState" CurrentStateChanged="PopupState_CurrentStateChanged">
          <vsm:VisualStateGroup.Transitions>
           <vsm:VisualTransition GeneratedDuration="00:00:00.2000000"/>
           <vsm:VisualTransition GeneratedDuration="00:00:00.2000000" To="PopupStateOpened"/>
           <vsm:VisualTransition GeneratedDuration="00:00:00.2000000" To="PopupStateClosed"/>
          </vsm:VisualStateGroup.Transitions>
          <vsm:VisualState x:Name="PopupStateClosed">
           <Storyboard>
            <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="PopupFillGrid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
             <SplineColorKeyFrame KeyTime="00:00:00" Value="#00FFFFFF"/>
            </ColorAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
             <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.5"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
             <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.5"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.Opacity)">
             <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
           </Storyboard>
          </vsm:VisualState>
                <vsm:VisualState x:Name="PopupStateOpened">
                    <Storyboard>
                        <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="PopupFillGrid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
                            <SplineColorKeyFrame KeyTime="00:00:00" Value="#BFFFFFFF"/>
                        </ColorAnimationUsingKeyFrames>
                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
                            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
                        </DoubleAnimationUsingKeyFrames>
                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
                            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
                        </DoubleAnimationUsingKeyFrames>
                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.Opacity)">
                            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </vsm:VisualState>
            </vsm:VisualStateGroup>
        </vsm:VisualStateManager.VisualStateGroups>

        <Popup x:Name="PopupControl">
            <Grid x:Name="PopupFillGrid">
                <Grid.Background>
                 <SolidColorBrush Color="#00ffffff"/>
                </Grid.Background>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>

                <Grid.RowDefinitions>
                    <RowDefinition/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition/>
                </Grid.RowDefinitions>

                <Border Grid.Row="1" Grid.Column="1" RenderTransformOrigin="0.5,0.5" x:Name="border" Opacity="0" >
                    <Border.RenderTransform>
                     <TransformGroup>
                      <ScaleTransform ScaleX="0.5" ScaleY="0.5"/>
                      <SkewTransform/>
                      <RotateTransform/>
                      <TranslateTransform/>
                     </TransformGroup>
                    </Border.RenderTransform>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="40"/>
                        </Grid.RowDefinitions>

                        <ContentPresenter x:Name="PopupContentPresenter"/>

                        <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
                            <Button x:Name="Button1" Click="Button1_Click" Margin="4" MinWidth="80">
                                <ContentPresenter x:Name="Button1ContentPresenter"/>
                            </Button>
                            <Button x:Name="Button2" Click="Button2_Click" Margin="4" MinWidth="80">
                                <ContentPresenter x:Name="Button2ContentPresenter"/>
                            </Button>
                            <Button x:Name="Button3" Click="Button3_Click"  Margin="4" MinWidth="80">
                                <ContentPresenter x:Name="Button3ContentPresenter"/>
                            </Button>
                        </StackPanel>
                    </Grid>
                </Border>
            </Grid>
        </Popup>
    </Grid>
</UserControl>

and its code-behind

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace SilverlightPopupControlTest
{
    public partial class PopupMessage : UserControl
    {
        #region PopupContent Dependency Property
        public UIElement PopupContent
        {
            get { return (UIElement)GetValue(PopupContentProperty); }
            set { SetValue(PopupContentProperty, value); }
        }
        public static readonly DependencyProperty PopupContentProperty = DependencyProperty.Register(
            "PopupContent", typeof(UIElement), typeof(PopupMessage), new PropertyMetadata(PopupContentChanged));


        private static void PopupContentChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((PopupMessage)o).OnPopupContentChanged((UIElement)e.OldValue, (UIElement)e.NewValue);
        }

        private void OnPopupContentChanged(UIElement oldValue, UIElement newValue)
        {
            this.PopupContentPresenter.Content = newValue;
        }
        #endregion
        #region Button1Visibility Dependency Property
        public Visibility Button1Visibility
        {
            get { return (Visibility)GetValue(Button1VisibilityProperty); }
            set { SetValue(Button1VisibilityProperty, value); }
        }
        public static readonly DependencyProperty Button1VisibilityProperty = DependencyProperty.Register(
            "Button1Visibility", typeof(Visibility), typeof(PopupMessage), new PropertyMetadata(Button1VisibilityChanged));


        private static void Button1VisibilityChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((PopupMessage)o).OnButton1VisibilityChanged((Visibility)e.OldValue, (Visibility)e.NewValue);
        }

        private void OnButton1VisibilityChanged(Visibility oldValue, Visibility newValue)
        {
            this.Button1.Visibility = newValue;
        }
        #endregion
        #region Button2Visibility Dependency Property
        public Visibility Button2Visibility
        {
            get { return (Visibility)GetValue(Button2VisibilityProperty); }
            set { SetValue(Button2VisibilityProperty, value); }
        }
        public static readonly DependencyProperty Button2VisibilityProperty = DependencyProperty.Register(
            "Button2Visibility", typeof(Visibility), typeof(PopupMessage), new PropertyMetadata(Button2VisibilityChanged));


        private static void Button2VisibilityChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((PopupMessage)o).OnButton2VisibilityChanged((Visibility)e.OldValue, (Visibility)e.NewValue);
        }

        private void OnButton2VisibilityChanged(Visibility oldValue, Visibility newValue)
        {
            this.Button2.Visibility = newValue;
        }
        #endregion
        #region Button3Visibility Dependency Property
        public Visibility Button3Visibility
        {
            get { return (Visibility)GetValue(Button3VisibilityProperty); }
            set { SetValue(Button3VisibilityProperty, value); }
        }
        public static readonly DependencyProperty Button3VisibilityProperty = DependencyProperty.Register(
            "Button3Visibility", typeof(Visibility), typeof(PopupMessage), new PropertyMetadata(Button3VisibilityChanged));


        private static void Button3VisibilityChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((PopupMessage)o).OnButton3VisibilityChanged((Visibility)e.OldValue, (Visibility)e.NewValue);
        }

        private void OnButton3VisibilityChanged(Visibility oldValue, Visibility newValue)
        {
            this.Button3.Visibility = newValue;
        }
        #endregion
        #region Button1Content Dependency Property
        public UIElement Button1Content
        {
            get { return (UIElement)GetValue(Button1ContentProperty); }
            set { SetValue(Button1ContentProperty, value); }
        }
        public static readonly DependencyProperty Button1ContentProperty = DependencyProperty.Register(
            "Button1Content", typeof(UIElement), typeof(PopupMessage), new PropertyMetadata(Button1ContentChanged));


        private static void Button1ContentChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((PopupMessage)o).OnButton1ContentChanged((UIElement)e.OldValue, (UIElement)e.NewValue);
        }

        private void OnButton1ContentChanged(UIElement oldValue, UIElement newValue)
        {
            this.Button1ContentPresenter.Content = newValue;
        }
        #endregion
        #region Button2Content Dependency Property
        public UIElement Button2Content
        {
            get { return (UIElement)GetValue(Button2ContentProperty); }
            set { SetValue(Button2ContentProperty, value); }
        }
        public static readonly DependencyProperty Button2ContentProperty = DependencyProperty.Register(
            "Button2Content", typeof(UIElement), typeof(PopupMessage), new PropertyMetadata(Button2ContentChanged));


        private static void Button2ContentChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((PopupMessage)o).OnButton2ContentChanged((UIElement)e.OldValue, (UIElement)e.NewValue);
        }

        private void OnButton2ContentChanged(UIElement oldValue, UIElement newValue)
        {
            this.Button2ContentPresenter.Content = newValue;
        }
        #endregion
        #region Button3Content Dependency Property
        public UIElement Button3Content
        {
            get { return (UIElement)GetValue(Button3ContentProperty); }
            set { SetValue(Button3ContentProperty, value); }
        }
        public static readonly DependencyProperty Button3ContentProperty = DependencyProperty.Register(
            "Button3Content", typeof(UIElement), typeof(PopupMessage), new PropertyMetadata(Button3ContentChanged));


        private static void Button3ContentChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((PopupMessage)o).OnButton3ContentChanged((UIElement)e.OldValue, (UIElement)e.NewValue);
        }

        private void OnButton3ContentChanged(UIElement oldValue, UIElement newValue)
        {
            this.Button3ContentPresenter.Content = newValue;
        }
        #endregion


        public enum PopupMessageResult
        {
            Unknown=0,
            Button1=1,
            Button2,
            Button3
        }

        #region PopupMessageClosed Event
        public class PopupMessageClosedEventArgs : EventArgs
        {
            public PopupMessage.PopupMessageResult Result { get; set; }

            public PopupMessageClosedEventArgs()
            {
            }

            public PopupMessageClosedEventArgs(PopupMessage.PopupMessageResult _Result)
            {
                this.Result = _Result;
            }

        }

        public delegate void PopupMessageClosedEventHandler(object sender, PopupMessageClosedEventArgs e);

        public event PopupMessageClosedEventHandler PopupMessageClosed;

        protected virtual void OnPopupMessageClosed(PopupMessageClosedEventArgs e)
        {
            if (PopupMessageClosed != null)
            {
                PopupMessageClosed(this, e);
            }
        }
        #endregion

        private PopupMessageResult m_Result = PopupMessageResult.Unknown;
        public PopupMessageResult Result
        {
            get { return m_Result; }
            set { m_Result = value; }
        }

        private FrameworkElement m_HostControl = null;
        public FrameworkElement HostControl
        {
            get { return m_HostControl; }
            set { m_HostControl = value; }
        }



        public PopupMessage()
        {
            InitializeComponent();

            this.Button1Visibility = Visibility.Collapsed;
            this.Button2Visibility = Visibility.Collapsed;
            this.Button3Visibility = Visibility.Collapsed;

            VisualStateManager.GoToState(this, "PopupStateClosed", false);
        }


        private void Close()
        {
            VisualStateManager.GoToState(this, "PopupStateClosed", true);
        }

        public void Show()
        {

            this.PopupControl.IsOpen = true;
            this.Visibility = Visibility.Visible;
            this.TabNavigation = KeyboardNavigationMode.Cycle;
            this.Button1.Focus();

            VisualStateManager.GoToState(this, "PopupStateOpened", true);
        }


        void PopupState_CurrentStateChanged(object sender, VisualStateChangedEventArgs e)
        {
            if (e.NewState.Name == "PopupStateClosed")
            {
                this.PopupControl.IsOpen = false;
                this.Visibility = Visibility.Collapsed;

                this.OnPopupMessageClosed(new PopupMessageClosedEventArgs(this.Result));
            }
        }

        private void Button1_Click(object sender, RoutedEventArgs e)
        {
            this.Result = PopupMessageResult.Button1;
            this.Close();
        }

        private void Button2_Click(object sender, RoutedEventArgs e)
        {
            this.Result = PopupMessageResult.Button2;
            this.Close();
        }

        private void Button3_Click(object sender, RoutedEventArgs e)
        {
            this.Result = PopupMessageResult.Button3;
            this.Close();
        }
    }
}
+2  A: 
Peter McGrattan
Thank you so much for your help. It's highlighted that I'm doing Bad Things in my main app.
geofftnz
So the problem was that the popupHost wasnt being removed from the visual tree?
geofftnz
Yes - Button1ContentPresenter was not removed with the call to Children.Clear. Have a look at the Silverlight version of UIElementCollection (the Type of Children) in Reflector and you'll see Clear does not call Remove on each element whereas Remove calls _visualChildren.Remove(element)
Peter McGrattan
Thanks for the tip. In my app the pages hosting the popups are within a ViewBox, which has Child rather than Children and no way that I can see of doing a Remove() other than setting to null.
geofftnz
I ended up removing the ContentPresenters from the Buttons and just set their content directly. This appears to have worked, however I'm not sure if nasty things are going on behind the scenes. I found some interesting things while playing with the testbench.
geofftnz
... One thing I found was that if you make the changes posted above, then add another private member called popupHost2, then make a new button to show it same as the other one, you'll see the same error when showing the second popup (even though it's another instance)
geofftnz
+1  A: 

FYI, as part of the new Silverlight Toolkit March 2009 release we've shipped a Picker base class. That Picker class provides popup functionality without you having to manually position and show the popup.

If you're interested, give me a shoutout and i'll do my best to post a quick tutorial here on how to use Picker. Basically: 1. Create a class that inherits from Picker 2. In Blend, edit the template of the control 2.1. Inside the popup in the template, put whatever you want to have in the popup (like buttons or something) 2.2. Next to the toggle button in the template put whatever visualization you want to demonstrate the value of the popup when it's closed. 3. Expose TemplateParts for the elements you create in 2.1 and 2.2. 4. in OnApplyTemplate grab instances of those TemplateParts with GetTemplatePart 5. Register for events on these controls to respond to their changes and update the control.

JustinAngel
A: 

Justin,

I would love to use the new Picker base class for a custom DatePicker control I want to create. But other than your 5 step process, I couldn't find any working examples of it. It seems non of the other Picker controls use it yet.

Do you have a sample app/control that I could use to save me a few hours of trial and error?

Much appreciated