Skip to main content

Microsoft Silverlight

Answered Question Problem arranging elements as children of a Canvas within a Canvas.RSS Feed

(0)

shacktoms
shacktoms

Member

Member

185 points

149 Posts

Problem arranging elements as children of a Canvas within a Canvas.

I am trying to write a control that arranges FrameworkElements, some of which come in from Xaml as subordinate elements, and some of which are generated by my control. The code seems to run fine for real, but fails in the Design view.

The issue seems to be that I can add a specified FrameworkElement to a child Canvas, but not to a child Canvas of a child Canvas.

I have simplified a test case to the following. (To see it fail, uncomment the #define at the beginning of Container.cs, rebuild, and view Page.xaml in the Design view).

Page.xaml...

<UserControl x:Class="PlaceElement.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:me="clr-namespace:PlaceElement;assembly=PlaceElement"
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <me:Container>
            <Ellipse Width="100" Height="100" Fill="Red"/>
        </me:Container>
    </Grid>
</UserControl>

Container.cs...

//#define crash
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
namespace PlaceElement {
    [ContentProperty("VisualObjectsCollection")]
    public class Container : UserControl {
        public static readonly DependencyProperty VisualObjectsCollectionProperty =
            DependencyProperty.Register("VisualObjectsCollection", typeof(ObservableCollection), typeof(Container), null);
        public ObservableCollection VisualObjectsCollection {
            get { return GetValue(VisualObjectsCollectionProperty) as ObservableCollection; }
            set { SetValue(VisualObjectsCollectionProperty, value); }
            }
        Canvas RootCanvas;
#if crash
        protected override Size MeasureOverride(Size availableSize) {
            RootCanvas.Children.Clear();
            Canvas InnerCanvas = new Canvas();
            foreach (var elt in VisualObjectsCollection) {
                InnerCanvas.Children.Add(elt);
                }
            RootCanvas.Children.Add(InnerCanvas);
            RootCanvas.Measure(availableSize);
            return RootCanvas.DesiredSize;
            }
#else
        protected override Size MeasureOverride(Size availableSize) {
            RootCanvas.Children.Clear();
            foreach (var elt in VisualObjectsCollection) {
                RootCanvas.Children.Add(elt);
                }
            RootCanvas.Measure(availableSize);
            return RootCanvas.DesiredSize;
            }
#endif
        protected override Size ArrangeOverride(Size finalSize) {
            RootCanvas.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));
            return finalSize;
            }
        public Container() {
            VisualObjectsCollection = new ObservableCollection();
            RootCanvas = new Canvas();
            Content = RootCanvas;
            }
        }
    }

shacktoms
shacktoms

Member

Member

185 points

149 Posts

Re: Problem arranging elements as children of a Canvas within a Canvas.

I was able to narrow the bug (or at least very confusing behavior) down to a single line difference. The whole issue appears to be that I cannot create the inner canvas within the MeasureOverride, otherwise things work OK. It doesn't help me because I have been wrapping items in Canvas in order to be able to Arrange (and apply a RenderTransform to) them without altering their design parameters. Maybe there is a better way to do that.

//#define crash
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;

namespace PlaceElement {
    [ContentProperty("VisualObjectsCollection")]
    public class Container : UserControl {
        public static readonly DependencyProperty VisualObjectsCollectionProperty =
            DependencyProperty.Register("VisualObjectsCollection", typeof(ObservableCollection), typeof(Container), null);
        public ObservableCollection VisualObjectsCollection {
            get { return GetValue(VisualObjectsCollectionProperty) as ObservableCollection; }
            set { SetValue(VisualObjectsCollectionProperty, value); }
            }

        Canvas RootCanvas;
        Canvas InnerCanvas;

        protected override Size MeasureOverride(Size availableSize) {
            RootCanvas.Children.Clear();

#if crash
            InnerCanvas = new Canvas();
#endif
            InnerCanvas.Children.Clear();
            RootCanvas.Children.Add(InnerCanvas);

            foreach (var elt in VisualObjectsCollection) {
                InnerCanvas.Children.Add(elt);
                }

            RootCanvas.Measure(availableSize);
            return RootCanvas.DesiredSize;
            }

        protected override Size ArrangeOverride(Size finalSize) {
            RootCanvas.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));
            return finalSize;
            }

        public Container() {
            VisualObjectsCollection = new ObservableCollection();
            RootCanvas = new Canvas();
            InnerCanvas = new Canvas();
            Content = RootCanvas;
            }
        }
    }

Yi-Lun Luo - MSFT
Yi-Lun L...

All-Star

All-Star

25052 points

2,747 Posts

Re: Problem arranging elements as children of a Canvas within a Canvas.

Hello, please don't modify the element tree in MeasureOverride and ArrangeOverride. Modifying element tree is likely to cause further layout update, and may have side effects on your layout logic. Why not add the Controls in the setter of your VisualObjectsCollection property?

public ObservableCollection<UIElement> VisualObjectsCollection
{
get { return GetValue(VisualObjectsCollectionProperty) as ObservableCollection<UIElement>; }
set
{
SetValue(VisualObjectsCollectionProperty, value);
SetControls();
}
}

public Container()
{
VisualObjectsCollection = new ObservableCollection<UIElement>();
RootCanvas = new Canvas();
InnerCanvas = new Canvas();
Content = RootCanvas;
this.Loaded += new RoutedEventHandler(Container_Loaded);
}

void Container_Loaded(object sender, RoutedEventArgs e)
{
SetControls();
}

private void SetControls()
{
//If you set VisualObjectsCollection in XAML, the setter will be called before RootCanvas is assigned a value. In this case, let's modify the Controls in Loaded event handler.
if (RootCanvas != null)
{
RootCanvas.Children.Clear();
InnerCanvas.Children.Clear();
RootCanvas.Children.Add(InnerCanvas);

foreach (var elt in VisualObjectsCollection)
{
InnerCanvas.Children.Add(elt);
}
}
}

 

 

shanaolanxing - I'll transfer to the Windows Azure team, and will have limited time to participate in the Silverlight forum. Apologize if I don't answer your questions in time.

shacktoms
shacktoms

Member

Member

185 points

149 Posts

Re: Problem arranging elements as children of a Canvas within a Canvas.

Actually, I do now have a workaround, which does involve pre-allocating all of the intemediate objects I may need before the MeasureOverride step, but the bug is still a bug, and it causes me to allocate more objects than I may end up needing.  I still think it is kind of unfortunate that I need these intemediate objects at all. If only Arrange took an additional argument that supplied a render transform I wouldn't need them.

However, I don't think I can avoid altering the Visual Tree during MeasureOverride and maybe also ArrangeOverride.
 
The problem with doing all the layout in advance is that in my actual application, the number and arrangement of the elements that are going to be displayed depends on the dimensions of the area the Control is going to use--the availableSize and the finalSize.  I do the editing of the visual tree in MeasureOverride because that is when I first find out the dimensions of the available area.

I don't see how I can complete all modification of the visual tree before I know what the display dimensions of my object are going to be.  I don't even know precisely what I am going to include in the visual tree until I know how much space I have to fill.

Furthermore I don't see how I am supposed to accomplish the purpose of ArrangeOverride, except to alter properties of elements within the visual tree.

Also, if I move the changes to the visual tree outside of MeasureOverride and ArrangeOverride, they are surely going to trigger a LayoutUpdate, so have I really saved anything?  It actually appears (from counting the number of times MeasureOverride is called), that the optimum number of LayoutUpdates occurs when I do my editing of the visual tree inside MeasureOverride.   This, in fact, is what should be the case, since the Layout system should understand that the net effect of any change to the visual tree made during MeasureOverride will be reflected in the return value from MeasureOverride, and thus the layout system can avoid triggering extra layout updates in the case that the work is done there.  So I think that if moving the changes outside MeasureOverride actually were to improve the situation, that should be regarded as another bug.

The way the Silverlight programming interface is designed makes it seem that availableSize is going to be dynamic, that MeasureOverride might get called at any moment to adjust to a change in availableSize (perhaps because the dimensions of a container control are being animated), which in turn might impact my control's visual tree.   But the view you are presenting seems to claim that Silverlight's model is really static, that there is some static availableSize somewhere (where?) and I should be able to build my visual tree in accord with that, and then just do something very simple in the call to MeasureOverride and ArrangeOverride which does not involve changing the visual tree.

shacktoms
shacktoms

Member

Member

185 points

149 Posts

Answered Question

Re: Problem arranging elements as children of a Canvas within a Canvas.

I think I have a better workaround now, which is probably conforms better to the Silverlight model.  Earlier, I had thought that the problem was a state problem in the creation of the Canvas, but now I think that the problem arises because objects, once added to the visual tree, seemingly cannot be moved.  I think this is an inherent part of the Silverlight design (since, for example, the Loaded event is only called once and there is no Unloaded event).

MeasureOverride was getting called twice, and it was the second call that was causing the error, I couldn't do the "InnerCanvas.Children.Add(elt);" step because elt had already been added to the visual tree on the first call.  This was the actual source of my problem.

I was able to fix it by changing the VisualObjectsCollection to be an ObservableCollection<ControlTemplate>, and then creating my visual tree using objects derived from Control (I have a class called Templated that is derived from Control).   My MeasureOverride now looks like this...

protected override Size MeasureOverride(Size availableSize) {
    RootCanvas.Children.Clear();

    foreach (var elt in VisualObjectsCollection) {
        Control Inner = new Templated();
        Inner.Template = elt;
        RootCanvas.Children.Add(Inner);
        }

    RootCanvas.Measure(availableSize);
    return RootCanvas.DesiredSize;
    }

subbiah
subbiah

Member

Member

3 points

15 Posts

Re: Problem arranging elements as children of a Canvas within a Canvas.

Hi,

My doubt is,I have one Canvas which has more than one children had arranged in the consecutive manner...if i am changing the height and width for any one of the children,neighbouring controls kept at consecutively..like panel...if the panels children has changed know neighbouring controls margin get changed know..i need same manner ..help me out... 

Can u post ur source code..it will very helpful for me..

I am struggling with that .please help me out

  • Unanswered Question
  • Answered Question
  • Announcement
Microsoft Communities