2010-08-04 55 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或者不是數字,但是當標籤被呈現時,它顯然具有可見的大小。當標籤在屏幕上可見時,我試圖從按鈕按下運行此代碼,但是產生相同的結果。

是否因爲我使用的風格?數據綁定也許?是我做了什麼錯誤盲目明顯或做WPF Gremlins只是真的恨我嗎?

第二次更新:

經過進一步搜索,我發現,測量()只適用於原始元素的大小。如果一個控件模板通過實際添加控件來修改它,那麼最好的方法可能是測量每個控件,但我承認這不僅僅是一點點混亂。

編譯器必須有一些方法來測量控件的所有內容,因爲它必須用它來放置項目,例如堆棧面板。必須有一些方法來訪問它,但現在我完全沒有想法。

+0

你在哪裏運行這段代碼?在Window的構造函數中?如果是這樣,你必須等待控件渲染完成。 – Carlo 2010-08-04 00:22:54

回答

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;  
    })); 

更新

在回答您的評論。如果你想添加其他代碼,你可以按如下方式組織你的代碼。最重要的一點是,調度員調用確保你等到控件有你的代碼依賴於它們所呈現執行之前被渲染:

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

啊,這是非常好的謝謝。 是否存在某種事件或布爾值來檢查現有消息何時被處理?我有一些依賴於檢查這些值的代碼,它們需要在控件創建後按順序運行。 如果不是,我相信我可以找到某種解決方案。 – 2010-08-04 00:39:45

19

的控制不會有大小,直到WPF做了佈局通。我認爲這是異步發生的。如果你在構造函數中這樣做,你可以鉤住Loaded事件 - 佈局將在那時發生,並且你在構造函數中添加的任何控件都將被調整大小。

但是,另一種方法是要求控件計算它想要的大小。爲此,請致電Measure,並將其傳遞給建議的大小。在這種情況下,你想傳遞一個無限大(意味着控制可以像它喜歡大),因爲這就是畫布會在佈局傳遞辦:

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

呼叫來衡量沒有按」實際上改變了控件的Width,RenderSize或ActualWidth。它只是告訴標籤來計算它想要的大小,並將該值放入其DesiredSize(一個屬性,其唯一目的是保存最後的Measure調用的結果)。

+0

這是一個非常優雅的解決方案,但都適用於不同的情況,所以我會讓他們都設置爲答案。我認爲這個最適合我的問題,謝謝。 – 2010-08-05 22:35:21

+1

+1這個乾淨優雅的解決方案。 – 2012-05-14 19:57:25

+0

我有一個DataContext設置在我的控件上,這應該會導致控件比默認的'首選大小'高。我有一個'UserControl',它的高度爲'Auto'。 在這種情況下,這個答案似乎並不適用於我。設置DataContext後,我必須執行'UpdateLayout()'來獲得'grow'的高度。 – 2017-10-12 20:35:54