2010-08-04 5 views
11

Ich weiß, das ist eine beliebte Frage, aber ich konnte nichts finden, was genau beantwortet, aber ich entschuldige mich, wenn ich etwas bei meinen Recherchen verpasst habe.Messsteuerelemente erstellt zur Laufzeit in WPF

Ich versuche, den folgenden Code ein Steuerelement zur Laufzeit zu erstellen und dann messen mit (die Messungen werden dann um Marquee-Stil die Steuerelemente bewegen verwendet werden - jede Kontrolle eine andere Größe ist):

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; 

Der Code erstellt ein Label-Steuerelement und wendet einen Stil darauf an. Der Stil enthält meine Kontrollschablone, die an das Objekt "Aufgabe" bindet. Wenn ich den Artikel erstellen scheint es perfekt, aber wenn ich versuche, die Kontrolle zu messen eine der Eigenschaften unter Verwendung der oben habe ich die folgenden Ergebnisse erhalten (I Schritt durch und jede Eigenschaft wiederum inspizieren):

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

Gibt es eine So erhalten Sie die gerenderte Höhe und Breite eines Steuerelements, das Sie zur Laufzeit erstellen?

UPDATE:

Sorry für Ihre Lösungen als Antworten Abwahl. Sie arbeiten an einem grundlegenden Beispielsystem, das ich eingerichtet habe, aber scheinbar nicht mit meiner vollständigen Lösung.

Ich denke, es könnte etwas mit dem Stil zu tun haben, so leid für die Unordnung, aber ich paste die ganze Sache hier.

Zuerst Ressourcen:

<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> 

Und der Stil selbst:

<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> 

Auch der Code Ich verwende:

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

Jedes Mal, wenn der Das heißt, der Wert wird entweder als 0 zurückgegeben oder nicht eine Zahl, aber wenn das Etikett gerendert wird, hat es offensichtlich eine Größe, wie es sichtbar ist. Ich habe versucht, diesen Code mit einem Tastendruck auszuführen, wenn das Etikett auf dem Bildschirm sichtbar ist, aber das führt zu denselben Ergebnissen.

Ist es wegen des Stils, den ich verwende? Datenbindungen vielleicht? Ist das, was ich falsch gemacht habe, offensichtlich offensichtlich oder die WPF Gremlins nur wirklich hasse mich?

Zweite Update:

Nach weiterer Suche ich, dass die Maßnahme entdeckt habe() gilt nur für die ursprüngliche Elementgröße. Wenn eine Kontrollschablone dies ändert, indem sie tatsächlich Kontrollen hinzufügt, dann wäre die beste Methode wahrscheinlich, jeden zu messen, aber ich gebe zu, dass das mehr als ein bisschen unordentlich ist.

Der Compiler muss eine Möglichkeit haben, den gesamten Inhalt eines Steuerelements zu messen, da es zum Platzieren von Elementen in z. B. Stack-Panels verwendet werden muss. Es muss einen Weg geben, darauf zuzugreifen, aber im Moment habe ich keine Ideen mehr.

+0

Wohin führen Sie diesen Code? Im Konstruktor des Windows? Wenn dies der Fall ist, müssen Sie warten, bis die Steuerelemente gerendert sind. – Carlo

Antwort

1

Gelöst es!

Das Problem war in meinem XAML. Auf der höchsten Ebene der Vorlage meines Labels gab es einen übergeordneten Canvas, der kein Feld für die Höhe oder Breite hatte. Da diese Größe für die Kinder nicht geändert werden musste, wurde sie konstant auf 0,0 gesetzt. Durch Entfernen und Ersetzen des Stammknotens durch einen Rahmen, dessen Größe an die untergeordneten Elemente angepasst werden muss, werden die Felder für Höhe und Breite aktualisiert und bei Measure() - Aufrufen an meinen Code zurückgegeben.

3

Ich bin ziemlich neu zu WPF, aber hier ist mein Verständnis.

Wenn Sie Steuerelemente programmatisch aktualisieren oder erstellen, gibt es einen nicht offensichtlichen Mechanismus, der auch feuert (für einen Anfänger wie mich sowieso - obwohl ich Windows-Nachrichtenverarbeitung zuvor gemacht habe, hat mich das erwischt ...). Updates und das Erstellen von Steuerelementen stellen verknüpfte Nachrichten in der UI-Dispatch-Warteschlange bereit, wobei der wichtige Punkt ist, dass diese zu einem späteren Zeitpunkt verarbeitet werden. Bestimmte Eigenschaften hängen davon ab, dass diese Nachrichten z.B. Tatsächliche Breite. Die Nachrichten führen in diesem Fall dazu, dass das Steuerelement gerendert wird und die mit einem gerenderten Steuerelement verknüpften Eigenschaften aktualisiert werden.

Beim programmgesteuerten Erstellen von Steuerelementen ist nicht offensichtlich, dass eine asynchrone Nachrichtenverarbeitung stattfindet und dass Sie auf die Verarbeitung dieser Nachrichten warten müssen, bevor einige der Eigenschaften aktualisiert wurden, z. Tatsächliche Breite.

Wenn Sie warten auf Nachrichten, die vor Zugriff Actual verarbeitet werden, dann wird es aktualisiert wurden:

//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;  
    })); 
auf Ihren Kommentar

aktualisieren

Als Reaktion. Wenn Sie anderen Code hinzufügen möchten, können Sie Ihren Code wie folgt organisieren. Das wichtige Bit ist, dass der Dispatcher Aufruf sorgt dafür, dass Sie warten, bis die Kontrollen, bevor Sie Ihren Code gemacht wurden, die von ihnen abhängig sind gemacht ausführt:

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

Ah, das ist ausgezeichnet, danke. Gibt es ein Ereignis irgendeiner Art oder einen booleschen Wert, um zu überprüfen, ob die vorhandenen Nachrichten verarbeitet wurden? Ich habe über Code, der darauf angewiesen ist, diese Werte ebenfalls zu überprüfen, die nach der Erstellung der Steuerung sequenziell ausgeführt werden müssen. Wenn nicht, bin ich sicher, dass ich eine Lösung finden kann. –

19

Die Steuerung wird nicht eine Größe haben, bis WPF ein Layout Pass tut . Ich denke, das passiert asynchron. Wenn Sie dies in Ihrem Konstruktor tun, können Sie das Loaded-Ereignis haken - das Layout wird bis dahin geschehen sein, und alle Steuerelemente, die Sie im Konstruktor hinzugefügt haben, wurden skaliert.

Eine andere Möglichkeit besteht jedoch darin, die Steuerung zu fragen, welche Größe sie haben möchte. Um dies zu tun, rufen Sie Measure, und übergeben Sie eine vorgeschlagene Größe. In diesem Fall sollten Sie eine unendliche Größe passieren (dh die Steuerung kann so groß sein, wie es mag), denn das ist, was Leinwand im Layout Pass vorhat:

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

Der Aufruf ‚Maßnahme Doesn t Ändern Sie tatsächlich die Breite, RenderSize oder ActualWidth des Steuerelements. Es teilt dem Label lediglich mit, wie groß es sein soll, und schreibt diesen Wert in seine DesiredSize (eine Eigenschaft, deren einziger Zweck es ist, das Ergebnis des letzten Measure-Aufrufs zu halten).

+0

Das ist eine sehr elegante Lösung, aber beide sind für verschiedene Situationen geeignet, also werde ich beide als Antworten behalten. Ich denke, das passt am besten zu meinem Problem, danke. –

+1

+1 für diese saubere und elegante Lösung. –

+0

Ich habe einen DataContext-Satz auf meinem Steuerelement, der dazu führen sollte, dass das Steuerelement größer als die Standard-Vorzugsgröße wird. Ich habe ein 'UserControl' erstellt mit einer Höhe von' Auto'. Diese Antwort scheint für mich in dieser Situation nicht zu funktionieren. Ich musste 'UpdateLayout()' nach dem Setzen von DataContext tun, um die Höhe zu 'wachsen' zu bekommen. –