Popup duplicates

Nov 8, 2010 at 3:05 PM
Edited Nov 8, 2010 at 3:06 PM

Hi! I’m having a problem with duplicated silverlight popups and I have no idea how to solve it. Anyone recognize this scenario:

1. I have an ObservableCollection, MyCollection full of ChildViewModels.

2. I name an ItemsControl MyCollection.

3. ChildView looks like this:

<Grid x:Name="LayoutRoot" Background="White">
        <Popup IsOpen="{Binding IsOpen}">
            <Rectangle Width="100" Height="100" Fill="Violet" Opacity="0.05" />
        </Popup>
    </Grid>

 

4. I have a method that removes a ChildViewModel from MyCollection and inserts it a new location.


        public void Click()
        {
            MyCollection.Remove(MyChildView);
            MyCollection.Add(MyChildView);
            MyChildView.IsOpen = !MyChildView.IsOpen;
        }

 

5. I get a duplicated Popup window in the top left corner whenever I run Click().

I don’t get the duplication if I use code-behind.

Anyone know how to get around this?

Thanx

/Mike

Nov 9, 2010 at 9:36 AM

Hmm, thought it might be hard to understand what I mean. Here is the code, it's not much:

ShellView:

    <StackPanel Width="400" Height="400" x:Name="myPanel">
        <ItemsControl x:Name="MyCollection" />
            
        <Button Width="100" Height="30"
            cm:Message.Attach="[Event Click] = [Action Click];" />
    </StackPanel>

ShellViewModel:

        public ObservableCollection<ChildViewModel> MyCollection { getset; }
        public ChildViewModel MyChildView { getset; }

        public ShellViewModel()
        {
            MyCollection = new ObservableCollection<ChildViewModel>();
            MyChildView = new ChildViewModel();
            MyCollection.Add(MyChildView);

        }

        public void Click()
        {
            MyChildView.IsOpen = !MyChildView.IsOpen;
            MyCollection.Remove(MyChildView);
            MyCollection.Add(MyChildView);
        }

 

ChildView:

    <Grid x:Name="LayoutRoot" Background="White">
        <Popup IsOpen="{Binding IsOpen}">
            <Rectangle Width="100" Height="100" Fill="Violet" Opacity="0.05" />
        </Popup>
    </Grid>

ChildViewModel:

        private bool isOpen;
        public bool IsOpen
        {
            get
            {
                return isOpen;
            }
            set
            {
                isOpen = value; NotifyOfPropertyChange("IsOpen");
            }
        }

 

 


Nov 9, 2010 at 1:57 PM

Could you also provide the code-behind version? I would like to check if there are subtle differences in view instantiation.

Using CM the View gets cached along with the ViewModel instance, so the Popup instance should definitely be the same.
I'm not sure that the previously open "low-level window" is kept when you detach the Popup from a VisualTree (which happens as a consequence of MyChild removal from the collection)

Nov 9, 2010 at 2:40 PM

Sure:

MainPage.xaml:

        <Grid Width="500" Height="600" HorizontalAlignment="Center">
            <StackPanel>
                <ItemsControl ItemsSource="{Binding MyCollection}" />
                <Button Width="100" Height="20" Click="Button_Click" />
            </StackPanel>
        </Grid>

MainPage.xaml.cs

        public ObservableCollection<ChildControl> MyCollection { getset; }
        ChildControl Child { getset; }

        public MainPage()
        {
            MyCollection = new ObservableCollection<ChildControl>();
            DataContext = this;
            InitializeComponent();
            Child = new ChildControl();
            MyCollection.Add(Child);
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MyCollection.Remove(Child);
            MyCollection.Add(Child);
            Child.IsOpen = !Child.IsOpen;
        }

ChildControl.xaml:

    <Grid x:Name="LayoutRoot" Background="White">
        <Popup IsOpen="{Binding IsOpen}">
            <Rectangle Width="100" Height="100" Fill="BlueViolet" />
        </Popup>
    </Grid>

ChildControl.xaml.cs

        public ChildControl()
        {
            DataContext = this;
            InitializeComponent();
        }

        private bool isOpen = true;
        public bool IsOpen
        {
            get { return isOpen; }
            set
            {
                isOpen = value; OnPropertyChanged("IsOpen");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(thisnew PropertyChangedEventArgs(propertyName));
        }

 

 

Nov 10, 2010 at 10:05 AM

Multiple popup are shown because a fresh view instance (bound to a common VM) is created a each insertion/removal from the collection.
To allow CM to properly cache the view instance associated with the VM, you have to implement IViewAware in the VM (ChildViewModel in your scenario), or simply inherit from Screen.
Once the view is cached, the erroneous behavior disappears.

Nov 10, 2010 at 10:40 AM

Excellent :) Thanks for helping me.

Nov 16, 2010 at 4:16 PM

Sry, but I can't get it to work. When I inherit from Screen I get an exception here (red):

private static void SetContentProperty(object targetLocation, object view)
        {
            var type = targetLocation.GetType();
            var contentProperty = type.GetAttributes<ContentPropertyAttribute>(true)
                .FirstOrDefault() ?? new ContentPropertyAttribute("Content");

            type.GetProperty(contentProperty.Name)
                .SetValue(targetLocation, view, null);
        }

This method was called by:

        object IViewAware.GetView(object context)
        {
            object view;
            views.TryGetValue(context ?? ViewLocator.DefaultContext, out view);
            return view;
        }

This method was called by:

MyCollection.Add(MyChildView);

 

 I have no idea how to go about this. I tryed to implement IViewAware but I couldn't come up with another way to do it than the one already implemented in Screen.