Измерение средств управления создается во времени выполнения в WPF

Я распознаю, что это - популярный вопрос, но я не мог найти ничего, что ответило на это точно, но я приношу извинения, пропустил ли я что-то в своих поисках.

Я пытаюсь создать и затем измерить управление во времени выполнения с помощью следующего кода (измерения затем привыкнут к прокрутке стиля пунктирной рамки средства управления - каждое управление является различным размером):

Label lb = new Label();
lb.DataContext= task;
Style style = (Style)FindResource("taskStyle");
lb.Style = style;

cnvMain.Children.Insert(0,lb);

width = lb.RenderSize.Width;
width = lb.ActualWidth;
width = lb.Width;

Код создает Элемент управления "метка" и применяет стиль к нему. Стиль содержит мой шаблон управления, который связывает с объектной "задачей". Когда я создаю объект, появляется отлично, однако когда я пытаюсь измерить управление с помощью любого из свойств выше, я получаю следующие результаты (я ступаю через и осматриваю каждое свойство в свою очередь):

lb.Width = NaN
lb.RenderSize.Width = 0
lb.ActualWidth = 0

Там какой-либо путь состоит в том, чтобы получить представленную высоту и ширину управления, которое Вы создаете во времени выполнения?

ОБНОВЛЕНИЕ:

Извините за отмену выбора Ваших решений как ответы. Они работают над основной системой в качестве примера, которую я настроил, но по-видимому не с моим полным решением.

Я думаю, что это может быть что-то, чтобы сделать со стилем, очень жаль о путанице, но я вставляю всю вещь здесь.

Во-первых, ресурсы:

    <Storyboard x:Key="mouseOverGlowLeave">
        <DoubleAnimation From="8" To="0" Duration="0:0:1" BeginTime="0:0:2" Storyboard.TargetProperty="GlowSize" Storyboard.TargetName="Glow"/>
    </Storyboard>

    <Storyboard x:Key="mouseOverTextLeave">
        <ColorAnimation From="{StaticResource buttonLitColour}" To="Gray" Duration="0:0:3" Storyboard.TargetProperty="Color" Storyboard.TargetName="ForeColour"/>
    </Storyboard>

    <Color x:Key="buttonLitColour" R="30" G="144" B="255" A="255" />

    <Storyboard x:Key="mouseOverText">
        <ColorAnimation From="Gray" To="{StaticResource buttonLitColour}" Duration="0:0:1" Storyboard.TargetProperty="Color" Storyboard.TargetName="ForeColour"/>
    </Storyboard>

И сам стиль:

    <Style x:Key="taskStyle" TargetType="Label">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <Canvas x:Name="cnvCanvas">
                        <Border  Margin="4" BorderBrush="Black" BorderThickness="3" CornerRadius="16">
                            <Border.Background>
                                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                    <GradientStop Offset="1" Color="SteelBlue"/>
                                    <GradientStop Offset="0" Color="dodgerBlue"/>
                                </LinearGradientBrush>
                            </Border.Background>
                            <DockPanel Margin="8">
                                <DockPanel.Resources>
                                    <Style TargetType="TextBlock">
                                        <Setter Property="FontFamily" Value="corbel"/>
                                        <Setter Property="FontSize" Value="40"/>
                                    </Style>
                                </DockPanel.Resources>
                                <TextBlock VerticalAlignment="Center" Margin="4" Text="{Binding Path=Priority}"/>
                                <DockPanel DockPanel.Dock="Bottom">
                                    <Border Margin="4" BorderBrush="SteelBlue" BorderThickness="1" CornerRadius="4">
                                        <TextBlock Margin="4" DockPanel.Dock="Right" Text="{Binding Path=Estimate}"/>
                                    </Border>
                                    <Border Margin="4" BorderBrush="SteelBlue" BorderThickness="1" CornerRadius="4">
                                        <TextBlock Margin="4" DockPanel.Dock="Left" Text="{Binding Path=Due}"/>
                                    </Border>
                                </DockPanel>
                                <DockPanel DockPanel.Dock="Top">
                                    <Border Margin="4" BorderBrush="SteelBlue" BorderThickness="1" CornerRadius="4">
                                        <TextBlock  Margin="4" Foreground="LightGray" DockPanel.Dock="Left" Text="{Binding Path=List}"/>
                                    </Border>
                                    <Border Margin="4" BorderBrush="SteelBlue" BorderThickness="1" CornerRadius="4">
                                        <TextBlock Margin="4" Foreground="White" DockPanel.Dock="Left" Text="{Binding Path=Name}"/>
                                    </Border>
                                </DockPanel>
                            </DockPanel>
                        </Border>
                    </Canvas>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Кроме того, код я использую:

Label lb = new Label(); //the element I'm styling and adding
  lb.DataContext= task; //set the data context to a custom class
  Style style = (Style)FindResource("taskStyle"); //find the above style
  lb.Style = style;

  cnvMain.Children.Insert(0,lb); //add the style to my canvas at the top, so other overlays work
  lb.UpdateLayout(); //attempt a full layout update - didn't work
  Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>
  {
      //Synchronize on control rendering
      double width = lb.RenderSize.Width;
      width = lb.ActualWidth;
      width = lb.Width;
  })); //this didn't work either
  lb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); //nor did this
  double testHeight = lb.DesiredSize.Height;
  double testWidth = lb.RenderSize.Width; //nor did this
  width2 = lb.ActualWidth; //or this
  width2 = lb.Width; //or this

//positioning for my marquee code - position off-screen to start with
  Canvas.SetTop(lb, 20);
  Canvas.SetTop(lb, -999);

//tried it here too, still didn't work
  lb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  testHeight = lb.DesiredSize.Height;
//add my newly-created label to a list which I access later to operate the marquee
  taskElements.AddLast(lb);
//still didn't work
  lb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  testHeight = lb.DesiredSize.Height;
//tried a mass-measure to see if that would work - it didn't
  foreach (UIElement element in taskElements)
  {
      element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  }

Каждый раз, когда любого из тех называют, значение возвращено или как 0 или как не число, все же когда маркировка представляется им, ясно имеет размер, поскольку это видимо. Я попытался выполнить этот код от нажатия кнопки, когда маркировка видима экранный, но это приводит к тем же результатам.

Это из-за стиля, который я использую? Привязки данных, возможно? То, что я сделал неправильно ослепляюще очевидный, или Гремлины WPF просто действительно ненавидят меня?

Второе обновление:

После дальнейшего поиска я обнаружил, что Мера () только относится к исходному размеру элемента. Если бы шаблон управления изменяет это путем фактического добавления средств управления затем, лучший метод должен был бы, вероятно, измерить каждого, но я признаю, что это - больше, чем немного грязный.

Компилятор должен иметь некоторый способ измерить весь контент управления, поскольку это должно использовать это для размещения объектов в, например, панели стека. Должен быть некоторый способ получить доступ к нему, но на данный момент я полностью вне идей.

15
задан Nick Udell 8 August 2010 в 22:20
поделиться

3 ответа

Решил!

Проблема заключалась в моем XAML. На самом высоком уровне моего шаблона лейбла был родительский холст, у которого не было поля высоты или ширины. Поскольку это не должно было изменять его размер для своих дочерних элементов, он постоянно был установлен на 0,0. Удалив его и заменив корневой узел рамкой, размер которой должен изменяться, чтобы соответствовать его дочерним элементам, поля высоты и ширины обновляются и передаются обратно в мой код при вызовах Measure ().

1
ответ дан 1 December 2019 в 02:28
поделиться

У элемента управления не будет размера, пока WPF не выполнит передачу макета. Я думаю, что это происходит асинхронно. Если вы делаете это в своем конструкторе, вы можете подключить событие Loaded - к ​​тому времени макет будет создан, и все элементы управления, которые вы добавили в конструктор, будут иметь размер.

Однако есть еще один способ - попросить элемент управления вычислить, какого размера он хочет быть. Для этого вы вызываете Measure и передаете ему предлагаемый размер. В этом случае вы хотите передать бесконечный размер (это означает, что элемент управления может быть сколь угодно большим), поскольку это то, что Canvas собирается делать на проходе макета:

lb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Debug.WriteLine(lb.DesiredSize.Width);

Вызов Measure фактически не изменяет Width, RenderSize или ActualWidth элемента управления. Он просто сообщает Label, что нужно вычислить, какого размера он хочет быть, и поместить это значение в свой DesiredSize (свойство, единственной целью которого является хранение результата последнего вызова меры).

24
ответ дан 1 December 2019 в 02:28
поделиться

Я новичок в WPF, но вот мое понимание.

Когда вы обновляете или создаете элементы управления программно, возникает неочевидный механизм, который также срабатывает (во всяком случае, для новичка вроде меня - хотя я раньше занимался обработкой сообщений Windows, это меня зацепило ...). Обновления и создание элементов управления помещают связанные сообщения в очередь отправки пользовательского интерфейса, где важным моментом является то, что они будут обработаны в какой-то момент в будущем. Некоторые свойства зависят от обрабатываемых сообщений, например ActualWidth. Сообщения в этом случае вызывают визуализацию элемента управления, а затем обновляют свойства, связанные с визуализированным элементом управления.

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

Если вы дождетесь обработки существующих сообщений перед доступом к ActualWidth, то оно будет обновлено:

    //These operations will queue messages on dispatch queue
    Label lb = new Label();
    canvas.Children.Insert(0, lb);

    //Queue this operation on dispatch queue
    Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>
    {
        //Previous messages associated with creating and adding the control
        //have been processed, so now this happens after instead of before...
        double width = lb.RenderSize.Width;
        width = lb.ActualWidth;
        width = lb.Width;    
    }));

Обновить

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

    Label lb = new Label();
    canvas.Children.Insert(0, lb);

    Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>
    {
        //Synchronize on control rendering
    }));

    double width = lb.RenderSize.Width;
    width = lb.ActualWidth;
    width = lb.Width;

    //Other code...
3
ответ дан 1 December 2019 в 02:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: