2014-01-16 3 views
1

Я пытаюсь создать холст, который изменяет размеры его детей при изменении размера холста. Поэтому я создаю свой собственный класс, который наследует от canvas и переопределяет метод ArrangeOverride, где я устанавливаю позиции и размеры для всех детей, определенных в холсте. Все выглядит нормально, но когда я изменяю размер окна приложения, элементы не были изменены, чтобы исправить размер или положение.Изменение размеров элементов на холсте

Это упрощенный пример, который пытается привязать ее элементы правой границы холста:

Xaml Код:

<Border BorderBrush="Black" BorderThickness="1" Margin="10" SnapsToDevicePixels="True"> 
    <l:CustomPanel> 
     <Rectangle Fill="Red" /> 
     <Button>a</Button> 
    </l:CustomPanel> 
</Border> 

CustomPanel:

public class CustomPanel : Canvas 
{ 
    protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize) 
    { 
     var ret = base.ArrangeOverride(arrangeSize); 
     var top = 0; 

     foreach(UIElement child in Children) 
     {        
      Canvas.SetLeft(child, arrangeSize.Width - 20.0);     
      child.SetValue(WidthProperty, arrangeSize.Width - Canvas.GetLeft(child));     
      Canvas.SetTop(child, top); 
      child.SetValue(HeightProperty, 20.0); 
      top += 30; 
     } 
     return ret; 
    } 
} 

При изменении ширины окно, иногда холст выглядит как на этом изображении:

error

И потом, если я изменить высоту окна, элементы переместить в правильное положение

Что я делаю неправильно? Я попытался установить SnapsToDevicePixels в True, и это не сработает для меня. :-(

+0

Не могли бы вы объяснить, что именно вы пытаетесь достичь? Холст предназначен для абсолютного позиционирования своих детей. Он не должен изменять их размер. Следовательно, вы не должны выводить свою пользовательскую панель из Canvas, а вместо нее из Panel. Я почти уверен, что есть гораздо более чистое решение, чем это. – Clemens

+0

Кроме того, Panel должна * никогда не устанавливать свойство Width или Height (или любое другое) своих дочерних элементов. Установка ширины или высоты может даже создать другой макет на родительской панели. Вместо этого дочерние элементы * расположены *, вызывая UIElement.Arrange. – Clemens

+0

Причина этого в том, что я должен создать нечто вроде графа. Таким образом, существует много элементов, которые должны находиться в определенной (процентной) позиции, а «график» должен поддерживать изменение размера. Я думал, что могу рассчитать конкретное положение предметов в холсте. И я пересчитываю эти элементы после изменения размера окна. Есть ли у вас какая-то идея, как добиться такого поведения? – Adam

ответ

3

Ваша пользовательская панель должна состоять из Panel вместо Canvas и переопределять методы MeasureOverride и ArrangeOverride. Кроме того, он должен определить свои собственные прикрепленные свойства для макета дочерних элементов, например четыре свойства RelativeX, RelativeY, RelativeWidth и RelativeHeight, показанные ниже.

Он будет использоваться в XAML как это:

<local:RelativeLayoutPanel> 
    <Rectangle Fill="Red" 
       local:RelativeLayoutPanel.RelativeX="0.2" 
       local:RelativeLayoutPanel.RelativeY="0.1" 
       local:RelativeLayoutPanel.RelativeWidth="0.6" 
       local:RelativeLayoutPanel.RelativeHeight="0.8"/> 
</local:RelativeLayoutPanel> 

Вот реализация:

public class RelativeLayoutPanel: Panel 
{ 
    public static readonly DependencyProperty RelativeXProperty = DependencyProperty.RegisterAttached(
     "RelativeX", typeof(double), typeof(RelativeLayoutPanel), 
     new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); 

    public static readonly DependencyProperty RelativeYProperty = DependencyProperty.RegisterAttached(
     "RelativeY", typeof(double), typeof(RelativeLayoutPanel), 
     new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); 

    public static readonly DependencyProperty RelativeWidthProperty = DependencyProperty.RegisterAttached(
     "RelativeWidth", typeof(double), typeof(RelativeLayoutPanel), 
     new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); 

    public static readonly DependencyProperty RelativeHeightProperty = DependencyProperty.RegisterAttached(
     "RelativeHeight", typeof(double), typeof(RelativeLayoutPanel), 
     new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); 

    public static double GetRelativeX(UIElement element) 
    { 
     return (double)element.GetValue(RelativeXProperty); 
    } 

    public static void SetRelativeX(UIElement element, double value) 
    { 
     element.SetValue(RelativeXProperty, value); 
    } 

    public static double GetRelativeY(UIElement element) 
    { 
     return (double)element.GetValue(RelativeYProperty); 
    } 

    public static void SetRelativeY(UIElement element, double value) 
    { 
     element.SetValue(RelativeYProperty, value); 
    } 

    public static double GetRelativeWidth(UIElement element) 
    { 
     return (double)element.GetValue(RelativeWidthProperty); 
    } 

    public static void SetRelativeWidth(UIElement element, double value) 
    { 
     element.SetValue(RelativeWidthProperty, value); 
    } 

    public static double GetRelativeHeight(UIElement element) 
    { 
     return (double)element.GetValue(RelativeHeightProperty); 
    } 

    public static void SetRelativeHeight(UIElement element, double value) 
    { 
     element.SetValue(RelativeHeightProperty, value); 
    } 

    protected override Size MeasureOverride(Size availableSize) 
    { 
     availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity); 

     foreach (UIElement element in InternalChildren) 
     { 
      element.Measure(availableSize); 
     } 

     return new Size(); 
    } 

    protected override Size ArrangeOverride(Size finalSize) 
    { 
     foreach (UIElement element in InternalChildren) 
     { 
      element.Arrange(new Rect(
       GetRelativeX(element) * finalSize.Width, 
       GetRelativeY(element) * finalSize.Height, 
       GetRelativeWidth(element) * finalSize.Width, 
       GetRelativeHeight(element) * finalSize.Height)); 
     } 

     return finalSize; 
    } 
} 

Если вам не нужно четыре свойства макета быть независимо друг от друга Привязываемым или устанавливаемым по стилю и т. д., вы можете заменить их одним прикрепленным свойством типа Rect:

<local:RelativeLayoutPanel> 
    <Rectangle Fill="Red" local:RelativeLayoutPanel.RelativeRect="0.2,0.1,0.6,0.8"/> 
</local:RelativeLayoutPanel> 

с этим гораздо короче реализации:

public class RelativeLayoutPanel: Panel 
{ 
    public static readonly DependencyProperty RelativeRectProperty = DependencyProperty.RegisterAttached(
     "RelativeRect", typeof(Rect), typeof(RelativeLayoutPanel), 
     new FrameworkPropertyMetadata(new Rect(), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); 

    public static Rect GetRelativeRect(UIElement element) 
    { 
     return (Rect)element.GetValue(RelativeRectProperty); 
    } 

    public static void SetRelativeRect(UIElement element, Rect value) 
    { 
     element.SetValue(RelativeRectProperty, value); 
    } 

    protected override Size MeasureOverride(Size availableSize) 
    { 
     availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity); 

     foreach (UIElement element in InternalChildren) 
     { 
      element.Measure(availableSize); 
     } 

     return new Size(); 
    } 

    protected override Size ArrangeOverride(Size finalSize) 
    { 
     foreach (UIElement element in InternalChildren) 
     { 
      var rect = GetRelativeRect(element); 

      element.Arrange(new Rect(
       rect.X * finalSize.Width, 
       rect.Y * finalSize.Height, 
       rect.Width * finalSize.Width, 
       rect.Height * finalSize.Height)); 
     } 

     return finalSize; 
    } 
} 
+0

Это выглядит намного лучше, чем решение на холсте и работает должным образом. – Adam

+0

Прекрасно работает даже с элементом ItemsControl с DataTemplate. Затем нужно использовать Style Setter для прикрепленного свойства, потому что InternalChildren возвращает wrapping ContentPresenters – Tarnschaf

+0

@ Tarnschaf Я предполагаю, что вы хотите сказать, что если вы используете Panel как ItemsPanel элемента ItemsControl, и эта панель использует прикрепленные свойства для дочерних элементов для своего макета, вам придется установить эти прикрепленные свойства в ItemContainerStyle вместо ItemTemplate, чтобы применить их к контейнеру элемента (то есть ContentPresenter здесь). Это справедливо для всех панелей, которые используют прикрепленные свойства для макета, но не имеют ничего общего с свойством InternalChildren. См. [этот ответ] (http: // stackoverflow. com/a/22325266/1136211) для примера с Canvas. – Clemens

1

ОК, поэтому я попытался возиться с этим, и я нашел какой-то хакерский способ заставить его работать. Я не совсем понимаю, почему он работает таким образом (пока), но это работает для меня:

public class CustomPanel : Canvas 
{ 
    private bool isFirstArrange = true; 

    protected override Size ArrangeOverride(Size arrangeSize) 
    { 
     var ret = new Size(); 
     bool isFirstArrangeLocal = isFirstArrange; 
     if (isFirstArrangeLocal) 
     { 
      ret = base.ArrangeOverride(arrangeSize); 
      isFirstArrange = false; 
     } 

     var top = 0; 
     foreach (UIElement child in Children) 
     { 
      Canvas.SetLeft(child, arrangeSize.Width - 20.0); 
      child.SetValue(WidthProperty, arrangeSize.Width - Canvas.GetLeft(child)); 
      Canvas.SetTop(child, top); 
      child.SetValue(HeightProperty, 20.0); 

      top += 30; 
     } 

     if (!isFirstArrangeLocal) 
     { 
      ret = base.ArrangeOverride(arrangeSize); 
     } 

     return ret; 
    } 
} 

Так идея поставить ArrangeOverride() после цикла Еогеаспа во всех ситуациях, кроме первого вызова

первого вызова должен быть перед Еогеаспом или по какой-то причине, я получаю это:.

Invalid

+0

Он работает с этим простым примером, спасибо. Я пытаюсь сделать то же самое в своем реальном проекте и, к сожалению, пока не работает. :-( – Adam

0

Это может быть или не соответствовать вашим требованиям, но всякий раз, когда я слышу/читаю «изменение размера», я думаю об элементе управления, который был построен именно для этой цели; Viewbox. Используя этот класс, у вас есть несколько опций по изменению размера контента, но я думаю, что вам нужен метод по умолчанию Uniform.

Просто поместите все, что нужно, чтобы быть изменен в ViewBox и посмотреть, как вам это нравится ... это должен сделать работу хорошо и просто для вас:

<ViewBox Stretch="Uniform"> 
    <Canvas ... /> 
</ViewBox> 

Вы на самом деле не нужно чтобы добавить Stretch="Uniform", потому что это по умолчанию ... это просто для демонстрационных целей. Чтобы узнать больше, см. Страницу Viewbox Class на MSDN.

Смежные вопросы