none
ItemsPanelTemplate的VisualTree必须是单个元素 RRS feed

答案

  • WPF use the different way to mansure arrange the ItemPanel in the ItemsControl, we cannot access the Children of the panel before loading/applying the template to all items in the ItemsControl.

    So I suggest you to change the code as below:

    public class CircularPanel : Panel
    {
      public enum AlignmentOptions { Left, Center, Right };
      
      ...
      
      private static void RadiusChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void AngleItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void IsAnimatedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void AnimationDurationChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void InitialAngleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void AlignChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      
      ...
      
      protected override Size MeasureOverride(Size availableSize)
      ...
      protected override Size ArrangeOverride(Size finalSize)
      ...
      
      private void Animate()
      ...
      
      private void Refresh()
      ...
    }
    

     

    You should call the InvalidateVisual method to Re-Meansure Arrange the panel with its child elements.

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年1月10日 9:02
    版主
  • Thanks Bob,

    1. Your advice is greate, it works. I can normally run the project with MainWindow.Obviously, InvalidateVisual method could resolve the ItemPanelTemplate Issue with single visualtree element.

    2. A small pity still remains, it's the animated / refresh method, which does not work now with the changement of InvalidateVisual method instead of Refresh method and OnIsAnimatedChange Method.

    3. Your given circular panel sample is a good work, i'll study details.

    The changed cs content is as below:

    =====================================================================================

    using System;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Media.Animation;

    namespace SwatchWpf
    {
     public class CircularPanel : Panel
     {
      public enum AlignmentOptions { Left, Center, Right };

      public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(CircularPanel), new PropertyMetadata(CircularPanel.RadiusChanged));
      public static readonly DependencyProperty AngleItemProperty = DependencyProperty.Register("AngleItem", typeof(double), typeof(CircularPanel), new PropertyMetadata(CircularPanel.AngleItemChanged));
      public static readonly DependencyProperty IsAnimatedProperty = DependencyProperty.Register("IsAnimated", typeof(bool), typeof(CircularPanel), new PropertyMetadata(CircularPanel.IsAnimatedChanged));
      public static readonly DependencyProperty AnimationDurationProperty = DependencyProperty.Register("AnimationDuration", typeof(int), typeof(CircularPanel), new PropertyMetadata(CircularPanel.AnimationDurationChanged));
      public static readonly DependencyProperty InitialAngleProperty = DependencyProperty.Register("InitialAngle", typeof(double), typeof(CircularPanel), new PropertyMetadata(CircularPanel.InitialAngleChanged));
      public static readonly DependencyProperty AlignProperty = DependencyProperty.Register("Align", typeof(AlignmentOptions), typeof(CircularPanel), new PropertyMetadata(CircularPanel.AlignChanged));

      private static void RadiusChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void AngleItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      //private static void IsAnimatedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).OnIsAnimatedChanged(e); }
            private static void IsAnimatedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void AnimationDurationChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void InitialAngleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void AlignChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }

      [Category("Circular Panel")]
      public double Radius
      {
       get { return (double)this.GetValue(CircularPanel.RadiusProperty); }
       set { this.SetValue(CircularPanel.RadiusProperty, value); }
      }

      [Category("Circular Panel")]
      public double AngleItem
      {
       get { return (double)this.GetValue(CircularPanel.AngleItemProperty); }
       set { this.SetValue(CircularPanel.AngleItemProperty, value); }
      }

      [Category("Circular Panel")]
      public bool IsAnimated
      {
       get { return (bool)this.GetValue(CircularPanel.IsAnimatedProperty); }
       set { this.SetValue(CircularPanel.IsAnimatedProperty, value); }
      }

      [Category("Circular Panel")]
      public int AnimationDuration
      {
       get { return (int)this.GetValue(CircularPanel.AnimationDurationProperty); }
       set { this.SetValue(CircularPanel.AnimationDurationProperty, value); }
      }

      [Category("Circular Panel")]
      public double InitialAngle
      {
       get { return (double)this.GetValue(CircularPanel.InitialAngleProperty); }
       set { this.SetValue(CircularPanel.InitialAngleProperty, value); }
      }

      [Category("Circular Panel")]
      public AlignmentOptions Align
      {
       get { return (AlignmentOptions)this.GetValue(CircularPanel.AlignProperty); }
       set { this.SetValue(CircularPanel.AlignProperty, value); }
      }

      private void OnIsAnimatedChanged(DependencyPropertyChangedEventArgs e)
      {
       this.Animate();
       this.InvalidateVisual();
      }

      protected override Size MeasureOverride(Size availableSize)
      {
       Size resultSize = new Size(0, 0);

       foreach (UIElement child in this.Children)
       {
        child.Measure(availableSize);
        resultSize.Width = Math.Max(resultSize.Width, child.DesiredSize.Width);
        resultSize.Height = Math.Max(resultSize.Height, child.DesiredSize.Height);
       }

       resultSize.Width =
        double.IsPositiveInfinity(availableSize.Width) ?
        resultSize.Width : availableSize.Width;

       resultSize.Height =
        double.IsPositiveInfinity(availableSize.Height) ?
        resultSize.Height : availableSize.Height;

       this.Animate();

       return resultSize;
      }

      protected override Size ArrangeOverride(Size finalSize)
      {
       this.Refresh();
       return base.ArrangeOverride(finalSize);
      }

      private void Animate()
      {
       if (this.IsAnimated)
       {
        int index = 0;
        foreach (FrameworkElement element in this.Children)
        {
         element.Opacity = 0;
         Storyboard loadStoryboard = new Storyboard();
         int time = this.AnimationDuration * (index + 1);

         DoubleAnimation opacityAnimation = new DoubleAnimation();
         opacityAnimation.From = 0;
         opacityAnimation.To = 1;
         opacityAnimation.BeginTime = new TimeSpan(0, 0, 0, 0, time);
         opacityAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, this.AnimationDuration));
         Storyboard.SetTarget(opacityAnimation, element);
         Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath("(Opacity)"));
         loadStoryboard.Children.Add(opacityAnimation);

         loadStoryboard.Begin();
         index++;
        }
       }
      }

      private void Refresh()
      {
       int count = 0;
       if (double.IsNaN(this.Width))
       {
        this.Width = 200;
       }
       if (double.IsNaN(this.Height))
       {
        this.Height = 200;
       }

       foreach (FrameworkElement element in this.Children)
       {
        RotateTransform r = new RotateTransform();
        double alignX = 0;
        double alignY = 0;
        switch (this.Align)
        {
         case AlignmentOptions.Left:
          alignX = 0;
          alignY = 0;
          break;
         case AlignmentOptions.Center:
          alignX = element.DesiredSize.Width / 2;
          alignY = element.DesiredSize.Height / 2;
          break;
         case AlignmentOptions.Right:
          alignX = element.DesiredSize.Width;
          alignY = element.DesiredSize.Height;
          break;
        }
        r.CenterX = alignX;
        r.CenterY = alignY;
        r.Angle = (this.AngleItem * count++) - this.InitialAngle;
        element.RenderTransform = r;
        double x = this.Radius * Math.Cos(Math.PI * r.Angle / 180);
        double y = this.Radius * Math.Sin(Math.PI * r.Angle / 180);

        if (!(double.IsNaN(this.Width)) && !(double.IsNaN(this.Height)) && !(double.IsNaN(alignX)) && !(double.IsNaN(alignY)) && !(double.IsNaN(element.DesiredSize.Width)) && !(double.IsNaN(element.DesiredSize.Height)))
        {
         element.Arrange(new Rect(x + this.Width / 2 - alignX, y + this.Height / 2 - alignY, element.DesiredSize.Width, element.DesiredSize.Height));
        }
       }
      }
     }
    }

    =====================================================================================

    2012年1月11日 1:59

全部回复

  • Hi,

    After building the project, the Blend Designer can show the control correctly. Blend is a design tool, and you should build the logical code first, then the designer can identify the custom panel and show it in the designer correctly:

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年1月10日 3:54
    版主
  • I also recommend you to design the UI in Blend, and design the code in Visual Studio 2010. Since I found this project run with some exceptions, and the Visual Studio can help you to debug them, but the Blend's debug function is very weak.
    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年1月10日 3:57
    版主
  • Thank you very much, Bob Bao.

    I can also see the control view, and rebuild the project in Expression Blend again and again, but it always shows the error "ItemPanelTemplate...." while run the project.

    Maybe you could try to run the project? Could it work correctly to show the Window?(Trade the MainControl as a window).

    2012年1月10日 6:23
  • Contractly, the same MainControl.xaml in silverlight has no such error. Is it related to some *.dll (such as System.Windows.dll for silverlight and System.PresesntationFramework.dll for WPF, etc..), or related to namespace issue?

    I've tried put the ItemPanelTemplate xaml codes inside the Listbox content/property instead of as a usercontrol resource, it still shows the same issue.

    2012年1月10日 6:41
  • WPF use the different way to mansure arrange the ItemPanel in the ItemsControl, we cannot access the Children of the panel before loading/applying the template to all items in the ItemsControl.

    So I suggest you to change the code as below:

    public class CircularPanel : Panel
    {
      public enum AlignmentOptions { Left, Center, Right };
      
      ...
      
      private static void RadiusChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void AngleItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void IsAnimatedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void AnimationDurationChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void InitialAngleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void AlignChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      
      ...
      
      protected override Size MeasureOverride(Size availableSize)
      ...
      protected override Size ArrangeOverride(Size finalSize)
      ...
      
      private void Animate()
      ...
      
      private void Refresh()
      ...
    }
    

     

    You should call the InvalidateVisual method to Re-Meansure Arrange the panel with its child elements.

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年1月10日 9:02
    版主
  • By the way, I am glad to introduce one old thread. I did one circular panel sample similar with your code. http://social.msdn.microsoft.com/Forums/en/wpf/thread/baf9bc67-7531-4060-8d12-240a594b3ff8
    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年1月10日 9:06
    版主
  • Thanks Bob,

    1. Your advice is greate, it works. I can normally run the project with MainWindow.Obviously, InvalidateVisual method could resolve the ItemPanelTemplate Issue with single visualtree element.

    2. A small pity still remains, it's the animated / refresh method, which does not work now with the changement of InvalidateVisual method instead of Refresh method and OnIsAnimatedChange Method.

    3. Your given circular panel sample is a good work, i'll study details.

    The changed cs content is as below:

    =====================================================================================

    using System;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Media.Animation;

    namespace SwatchWpf
    {
     public class CircularPanel : Panel
     {
      public enum AlignmentOptions { Left, Center, Right };

      public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(CircularPanel), new PropertyMetadata(CircularPanel.RadiusChanged));
      public static readonly DependencyProperty AngleItemProperty = DependencyProperty.Register("AngleItem", typeof(double), typeof(CircularPanel), new PropertyMetadata(CircularPanel.AngleItemChanged));
      public static readonly DependencyProperty IsAnimatedProperty = DependencyProperty.Register("IsAnimated", typeof(bool), typeof(CircularPanel), new PropertyMetadata(CircularPanel.IsAnimatedChanged));
      public static readonly DependencyProperty AnimationDurationProperty = DependencyProperty.Register("AnimationDuration", typeof(int), typeof(CircularPanel), new PropertyMetadata(CircularPanel.AnimationDurationChanged));
      public static readonly DependencyProperty InitialAngleProperty = DependencyProperty.Register("InitialAngle", typeof(double), typeof(CircularPanel), new PropertyMetadata(CircularPanel.InitialAngleChanged));
      public static readonly DependencyProperty AlignProperty = DependencyProperty.Register("Align", typeof(AlignmentOptions), typeof(CircularPanel), new PropertyMetadata(CircularPanel.AlignChanged));

      private static void RadiusChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void AngleItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      //private static void IsAnimatedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).OnIsAnimatedChanged(e); }
            private static void IsAnimatedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void AnimationDurationChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void InitialAngleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }
      private static void AlignChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel)sender).InvalidateVisual(); }

      [Category("Circular Panel")]
      public double Radius
      {
       get { return (double)this.GetValue(CircularPanel.RadiusProperty); }
       set { this.SetValue(CircularPanel.RadiusProperty, value); }
      }

      [Category("Circular Panel")]
      public double AngleItem
      {
       get { return (double)this.GetValue(CircularPanel.AngleItemProperty); }
       set { this.SetValue(CircularPanel.AngleItemProperty, value); }
      }

      [Category("Circular Panel")]
      public bool IsAnimated
      {
       get { return (bool)this.GetValue(CircularPanel.IsAnimatedProperty); }
       set { this.SetValue(CircularPanel.IsAnimatedProperty, value); }
      }

      [Category("Circular Panel")]
      public int AnimationDuration
      {
       get { return (int)this.GetValue(CircularPanel.AnimationDurationProperty); }
       set { this.SetValue(CircularPanel.AnimationDurationProperty, value); }
      }

      [Category("Circular Panel")]
      public double InitialAngle
      {
       get { return (double)this.GetValue(CircularPanel.InitialAngleProperty); }
       set { this.SetValue(CircularPanel.InitialAngleProperty, value); }
      }

      [Category("Circular Panel")]
      public AlignmentOptions Align
      {
       get { return (AlignmentOptions)this.GetValue(CircularPanel.AlignProperty); }
       set { this.SetValue(CircularPanel.AlignProperty, value); }
      }

      private void OnIsAnimatedChanged(DependencyPropertyChangedEventArgs e)
      {
       this.Animate();
       this.InvalidateVisual();
      }

      protected override Size MeasureOverride(Size availableSize)
      {
       Size resultSize = new Size(0, 0);

       foreach (UIElement child in this.Children)
       {
        child.Measure(availableSize);
        resultSize.Width = Math.Max(resultSize.Width, child.DesiredSize.Width);
        resultSize.Height = Math.Max(resultSize.Height, child.DesiredSize.Height);
       }

       resultSize.Width =
        double.IsPositiveInfinity(availableSize.Width) ?
        resultSize.Width : availableSize.Width;

       resultSize.Height =
        double.IsPositiveInfinity(availableSize.Height) ?
        resultSize.Height : availableSize.Height;

       this.Animate();

       return resultSize;
      }

      protected override Size ArrangeOverride(Size finalSize)
      {
       this.Refresh();
       return base.ArrangeOverride(finalSize);
      }

      private void Animate()
      {
       if (this.IsAnimated)
       {
        int index = 0;
        foreach (FrameworkElement element in this.Children)
        {
         element.Opacity = 0;
         Storyboard loadStoryboard = new Storyboard();
         int time = this.AnimationDuration * (index + 1);

         DoubleAnimation opacityAnimation = new DoubleAnimation();
         opacityAnimation.From = 0;
         opacityAnimation.To = 1;
         opacityAnimation.BeginTime = new TimeSpan(0, 0, 0, 0, time);
         opacityAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, this.AnimationDuration));
         Storyboard.SetTarget(opacityAnimation, element);
         Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath("(Opacity)"));
         loadStoryboard.Children.Add(opacityAnimation);

         loadStoryboard.Begin();
         index++;
        }
       }
      }

      private void Refresh()
      {
       int count = 0;
       if (double.IsNaN(this.Width))
       {
        this.Width = 200;
       }
       if (double.IsNaN(this.Height))
       {
        this.Height = 200;
       }

       foreach (FrameworkElement element in this.Children)
       {
        RotateTransform r = new RotateTransform();
        double alignX = 0;
        double alignY = 0;
        switch (this.Align)
        {
         case AlignmentOptions.Left:
          alignX = 0;
          alignY = 0;
          break;
         case AlignmentOptions.Center:
          alignX = element.DesiredSize.Width / 2;
          alignY = element.DesiredSize.Height / 2;
          break;
         case AlignmentOptions.Right:
          alignX = element.DesiredSize.Width;
          alignY = element.DesiredSize.Height;
          break;
        }
        r.CenterX = alignX;
        r.CenterY = alignY;
        r.Angle = (this.AngleItem * count++) - this.InitialAngle;
        element.RenderTransform = r;
        double x = this.Radius * Math.Cos(Math.PI * r.Angle / 180);
        double y = this.Radius * Math.Sin(Math.PI * r.Angle / 180);

        if (!(double.IsNaN(this.Width)) && !(double.IsNaN(this.Height)) && !(double.IsNaN(alignX)) && !(double.IsNaN(alignY)) && !(double.IsNaN(element.DesiredSize.Width)) && !(double.IsNaN(element.DesiredSize.Height)))
        {
         element.Arrange(new Rect(x + this.Width / 2 - alignX, y + this.Height / 2 - alignY, element.DesiredSize.Width, element.DesiredSize.Height));
        }
       }
      }
     }
    }

    =====================================================================================

    2012年1月11日 1:59
  • Well, if you want to Refresh/Animat these elements also, you could call the UpdateLayout instead of the InvalidateVisual. Try it.

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年1月11日 6:38
    版主
  • Well, Bob, you're such a kind supporter of MSDN Forum. :)

     I've had a try, and it does not work. Seems UpdateLayout  has the same result as InvalidateVisual Method.

    Another question need your help, How to trigger a different event with each element  simliar as mouse click. For instance, in this project, while mouse click the "Theme Red Fire", it shows messagebox "Red Fire".

    2012年1月11日 7:29
  • You could specify one EventHandler on the Style for one kind of element, such as:

          <Style TargetType="{x:Type Rectangle}">
            <EventSetter Event="MouseDown" Event="...."/>
          </Style>
    

    And in the event handler, you could judge the e.Source/OriginalSource get the element who fire the event. And then call the corresponding method.

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    2012年1月11日 7:57
    版主