2010-08-04 4 views
11

이 질문은 대중적인 질문이지만 정확하게 대답 한 항목을 찾을 수는 없었지만 내 검색에서 뭔가를 놓친 경우 사과드립니다. 내가 만들고 다음 코드를 사용하여 런타임에 컨트롤을 측정하기 위해 노력하고있어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; 

코드는 Label 컨트롤을 만들고 스타일을 적용합니다. 스타일에는 개체 "작업"에 바인딩되는 제어 템플릿이 포함되어 있습니다.

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

가 어떤이다 : 나는 항목을 만들 때 나는 다음과 같은 결과를 (내가 단계별로 차례로 각 속성을 검사) 얻을 위에 나는 속성 중 하나를 사용하여 컨트롤을 측정하려고 그러나 때, 완벽하게 나타납니다 런타임에 생성하는 컨트롤의 렌더링 된 높이와 너비를 가져 오는 방법은 무엇입니까?

UPDATE : 답변으로 솔루션을 선택 해제를위한

죄송합니다. 그것들은 내가 설정 한 기본 예제 시스템에서 작동하지만, 내 완벽한 솔루션으로는 보이지 않습니다.

스타일과 관련이있을 수 있으므로 혼란스러워서 미안하지만 여기 모든 것을 붙여 넣습니다.

첫째, 자원 :

<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으로 반환됩니다. 또는 숫자가 아니지만 레이블이 렌더링 될 때 크기가 명확하게 표시됩니다. 라벨이 화면에 표시 될 때 버튼 누름에서이 코드를 실행 해 보았지만 동일한 결과가 나타납니다.

내가 사용하고있는 스타일 때문입니까? 데이터 바인딩은 아마도? 내가 뭘 잘못 blindingly 명백한하거나 그냥 WPF Gremlins 할 정말 날 싫어하나요?

두 번째 업데이트 : 그 측정을 (발견했습니다

더 검색 후에는)은 원래 요소 크기에 적용됩니다. 컨트롤 템플릿이 실제로 컨트롤을 추가하여이를 수정하면 가장 좋은 방법은 아마도 각각을 측정하는 것이지만, 나는 그것이 조금 이상하다는 것을 인정합니다.

컴파일러에는 스택 패널과 같이 항목을 배치 할 때 사용해야하는 컨트롤의 모든 내용을 측정하는 방법이 있어야합니다. 접근 할 수있는 방법이 있어야하지만, 지금은 완전히 아이디어가 없습니다.

+0

이 코드를 어디에서 실행하고 있습니까? 창 생성자에서? 그렇다면 컨트롤이 렌더링 될 때까지 기다려야합니다. – Carlo

답변

1

해결했습니다!

문제가 내 XAML에 있습니다. 내 레이블의 템플릿의 최상위 레벨에는 높이 또는 너비 필드가없는 상위 캔버스가있었습니다. 이것은 자식의 크기를 수정할 필요가 없었기 때문에 계속 0,0으로 설정되었습니다. 이를 제거하고 루트 노드를 자식 노드에 맞게 크기를 조정해야하는 테두리로 바꾸면 높이 및 너비 필드가 업데이트되고 Measure() 호출시 내 코드로 다시 전파됩니다.

3

저는 WPF에 처음 접했지만 여기에 대한 이해가 있습니다.

프로그래밍 방식으로 컨트롤을 업데이트하거나 만들면 나에게도 어쨌든 초보자 용으로 발사되는 비 명백한 메커니즘이 있습니다. 이전에 Windows 메시지 처리를 수행했지만이 기능을 사용하여 나를 잡았습니다 ...). 컨트롤의 업데이트 및 생성은 관련된 메시지를 UI 디스패치 대기열에 대기열에 둡니다. 중요한 점은 이러한 메시지가 향후 어느 시점에서 처리된다는 것입니다. 특정 속성은 처리중인 메시지에 따라 달라집니다. 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;  
    })); 

업데이트

귀하의 코멘트에 대응. 다른 코드를 추가하려면 다음과 같이 코드를 구성 할 수 있습니다. WPF는 레이아웃의 패스를 수행 할 때까지

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... 
+0

아, 정말 고맙습니다. 기존 메시지가 처리 된시기를 확인하기 위해 일종의 이벤트 또는 부울 값이 있습니까? 이러한 값을 확인하는 데 의존하는 코드는 컨트롤 생성 후 순차적으로 실행해야합니다. 그렇지 않은 경우 어떤 종류의 해결책을 찾을 수 있을지 확신합니다. –

19

제어는 크기가되지 않습니다 중요한 비트는 디스패처 통화가 컨트롤되는 렌더링 실행하는 그들에 따라 달라집니다 코드 앞에 렌더링 될 때까지 기다리는 것이 보장한다는 것입니다 . 나는 그것이 비동기 적으로 일어난다 고 생각한다. 생성자에서이 작업을 수행하는 경우 Loaded 이벤트를 후크 할 수 있습니다. 레이아웃은 그때까지 발생했으며 생성자에서 추가 한 컨트롤의 크기가 조정됩니다.

그러나 다른 방법은 컨트롤에 원하는 크기를 계산하도록 요청하는 것입니다. 그러려면 Measure으로 전화하여 제안 된 크기로 전달하십시오.

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

호출하는 것은 아무튼 측정하는 ':이 경우, 당신은이 캔버스 레이아웃 패스 할 것입니다 무엇 때문에, (컨트롤을 의미하는 것은 그것을 좋아하는만큼 클 수있다) 무한 크기를 전달하려는 실제로 컨트롤의 Width, RenderSize 또는 ActualWidth를 변경합니다. 단지 Label에 원하는 크기를 계산하고 그 값을 DesiredSize (마지막 측정 값 호출의 결과를 보유하는 것이 유일한 속성)에 넣으라고 지시합니다.

+0

그건 아주 우아한 해결책이지만, 둘 다 서로 다른 상황에 적용 할 수 있습니다. 나는 이것이 내 문제에 가장 잘 맞다고 생각한다. 고마워. –

+1

+1 깨끗하고 우아한 솔루션입니다. –

+0

컨트롤에 DataContext를 설정하면 컨트롤이 기본 '기본 크기'보다 커야합니다. 나는'Auto'의 높이로 생성 된'UserControl'을 가지고 있습니다. 이 상황에서이 대답은 저에게 도움이되지 않습니다. 'Grow'키를 얻으려면 DataContext를 설정 한 후에'UpdateLayout()'을 수행해야했습니다. –

관련 문제