Laden...

Zugriff auf Element nach angewendetetem Style

Erstellt von SteffoD vor 5 Jahren Letzter Beitrag vor 5 Jahren 3.087 Views
S
SteffoD Themenstarter:in
44 Beiträge seit 2018
vor 5 Jahren
Zugriff auf Element nach angewendetetem Style

Hallo an alle,

ich habe ein Stil, das ich auf einen Control anwende:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="ImageItemStyle" TargetType="{x:Type ContentControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ContentControl}">
                    <StackPanel>
                        <TextBlock Name="tbImageItemCaption" Text="{Binding Caption}" />
                        <Image Source="{Binding Image}" />
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Der Stil wird auf folgenden Control anwendet:

public class ImageItem : ContentControl
{
	public ImageItem()
	{
		DataContext = this;
	}

	public ImageSource Image { get; set; }
	public string Caption { get; set; }
}

Und so wird er angwendet:

var imageItemDictionary = new ResourceDictionary
{
	Source = new Uri("/DiagramDesigner;component/Stencils/ImageItemStyle.xaml",
					 UriKind.RelativeOrAbsolute)
};

var imageItemStyle = (Style)imageItemDictionary["ImageItemStyle"];

ImageItem imageItemControl = new ImageItem
{
	Style = imageItemStyle,
	Image = image,
	Caption = id,
};

Ich würde nun gerne auf das Element tbImageItemCaption im Stil zugreifen. Allerdings gelingt mir das nicht.

Beide Heransgehensweisen liefern null zurück:

var imageItemCaption1 = imageItemControl.ContentTemplate.FindName("tbImageItemCaption", imageItemControl);
var imageItemCaption2 = imageItemControl.FindName("tbImageItemCaption");

Eine weitere Möglichkeit wäre vielleicht das über Bindings zu lösen, allerdings wüsste icht nicht, wie das genau funktioniert.

Jemand eine Idee?

Danke im Voraus!

2.079 Beiträge seit 2012
vor 5 Jahren

Was Du da tust, sieht ein bisschen falsch aus 😉

Ein Control sollte DependencyProperties haben, anstatt normaler Properties, da sie sonst nicht als Binding-Ziel funktionieren. Siehe hier

Auch das Zuweisen des DataContext auf this lässt darauf schließen, dass dir nicht wirklich klar ist, was MVVM ist und wie man die Zusammenhänge richtig macht. Siehe hier

Warum weist Du den Style im Code zu? Es gibt mehrere Möglichkeiten, wie Du einen Style setzen kannst, ohne den Key explizit suchen zu müssen. Siehe hier

Auch solltest Du ein Control nicht per Code instanziieren müssen, es gibt viele Mittel und Wege (z.B. ItemsControl, Panels, etc.), wie das automatisch passieren kann. Natürlich gibt es auch immer wieder Situationen, wo man tatsächlich ein Control im CodeBehind instanziieren muss, aber das hab ich bisher eher selten gesehen.

Aber zu deiner eigentlichen Frage:
Das geht nur über TemplateParts. Siehe hier
Dein Problem wird wohl sein, dass Du zuerst versuchst, den TemplatePart vom ContentTemplate zud bekommen und danach vom Control selber, Du brauchst es aber vom Template.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
SteffoD Themenstarter:in
44 Beiträge seit 2018
vor 5 Jahren

Hi Palladin,007,

danke, für deine ausführliche Antwort. 😉
Vielleicht hätte ich mein eigentliches Problem erklären sollen. Ich würde gerne die TextBlock-Höhe in Pixeln ermitteln. Ich hatte mir erhofft, dass ich diese Eigenschaft über die Höhe von TextBlock ermitteln kann. Gibt es noch leichtere Möglichkeiten?

Ich danke im Voraus.

2.079 Beiträge seit 2012
vor 5 Jahren

Schon die ActualHeight-Property vom Control probiert? Wenn der TextBlock keine Abstände drum herum hat, sollte das ja passen, abgesehen davon ist das die einzige Höhe, die von außen interessieren sollte.

Aber wozu brauchst Du die Höhe?
Mir fällt spontan kein Scenario ein, wo man die Höhe braucht und das man nicht über andere Wege, wie z.B. mit einem Grid lösen kann.
Aber auch hier gibt's natürlich immer wieder Situationen, wo das durchaus sinnvoll sein kann, ich sag nur, dass es lohnt, doch den Blick in andere Richtungen zu lenken, bevor Du groß mit der Höhe herum zu rechnen.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
SteffoD Themenstarter:in
44 Beiträge seit 2018
vor 5 Jahren

Schon die ActualHeight-Property vom Control probiert? Wenn der TextBlock keine Abstände drum herum hat, sollte das ja passen, abgesehen davon ist das die einzige Höhe, die von außen interessieren sollte.

Ich habe dieses Property und auch Height bei imageItemControl abgefragt, aber da der Control zu diesem Zeitpunkt noch nicht sichtbar ist, ist es auf 0 bzw. im letzteren Fall auf _NaN _gesetzt.

Aber wozu brauchst Du die Höhe?

Eigentlich brauche ich die Gesamthöhe vom ImageItem-Control, die mir wie gesagt zu jenem Zeitpunkt noch nicht zur Verfügung steht. Ich brauche das, um die Position des nächsten ImageItem-Element zu berechnen.

S
SteffoD Themenstarter:in
44 Beiträge seit 2018
vor 5 Jahren

Aber zu deiner eigentlichen Frage:
Das geht nur über TemplateParts. Siehe
>

Dein Problem wird wohl sein, dass Du zuerst versuchst, den TemplatePart vom ContentTemplate zud bekommen und danach vom Control selber, Du brauchst es aber vom Template.

Mir ist leider nicht ganz klar, wie ich das umsetzen soll. Soll ich das Image-Element den Namen PART_ContentHost geben?

2.079 Beiträge seit 2012
vor 5 Jahren

Wenn das Element nicht sichtbar ist, kannst Du auch keine Höhe abfragen. Woher soll WPF die Höhe denn auch wissen, die ergibt sich doch erst, wenn es sichtbar ist?
Und die Height-Property ist nur zum Setzen einer expliziten Höhe, die tatsächliche Höhe steht nur in ActualHeight.

WPF ist so gebaut, dass es sich in vollem Umfang an den verfügbaren Platz anpassen kann, daher solltest Du auch nicht die endgültige Breite/Höhe berechnen, das kann WPF besser als Du.

Das Grid kann sehr viel, Du kannst einzelnen Spalten und Zeilen eine feste Breite/Höhe geben, auch eine relative Breite/Höhe, oder eine, die sich an den Inhalt anpasst. Mit mehreren Grids übereinander und, noch extremer, der SharedSizeGroup, kannst Du komplexe Oberflächen gestalten, die sich automatisch an den Rest anpasst, ohne dass Du auch nur eine Zeile rechnen musst.
Hilfreich sind da auch ItemsControl und Ableitungen, und natürlich allgemein Panel, wie StackPanel, DockPanel oder eben das Grid.

Wenn Du unbedingt selber rechnen willst, musst Du warten, bis WPF fertig gezeichnet hat und dann musst Du auch darauf achten, dass sich der Container, in dem sich deine Controls befinden, ändern kann und Du entsprechend neu rechnen musst.

Mir ist leider nicht ganz klar, wie ich das umsetzen soll. Soll ich das Image-Element den Namen PART_ContentHost geben?

Der Name ist prinzipiell erst einmal egal, aber es bietet sich natürlich an, einen Namen zu verwenden, den man direkt als TemplatePart erkennt - eben indem er mit "PART_" beginnt.
Wichtig ist, dass Du den TemplatePart auch am Template suchst und nicht am ContentTemplate oder dem Control, da wirst Du das natürlich nicht finden.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
SteffoD Themenstarter:in
44 Beiträge seit 2018
vor 5 Jahren

Hi Palladin007,

um ein besseres Bild zu bekommen: Ich implementiere eine Art Designer. Die Elemente sind frei bewegbar, skalierbar und man kann auch zoomen. Da würde mir Grid nur im Weg stehen, oder?

Dann werde ich wohl versuchen zu rechnen, wenn das Control fertig gezeichnet hat. Danke. 😃

2.079 Beiträge seit 2012
vor 5 Jahren

Das wiederum ist vermutlich einer der wenigen Situationen, wo man tatsächlich selber rechnen muss 😄

Da dürfte das Canvas sich anbieten, da kann man die Elemente explizit positionieren, ohne dass WPF sich einmischt.

Aber ja, Du musst warten, bis fertig gezeichnet wurde und dann die Positionen anpassen. Wenn ich mich richtig erinnere, müsste es das Loaded-Event sein, was Du suchst.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

5.658 Beiträge seit 2006
vor 5 Jahren

Hi SteffoD,

insgesamt kann man sagen, die beste Herangehensweise wäre,

  • eine Canvas zu verwenden, auf der man die Elemente frei positionieren kann uns
  • MVVM und DataBinding zu verwenden (anstatt Elemente im Code Behind per Style o.ä. suchen), oder
  • ein fertiges Control dafür benutzen (gibt es im Netz jede Menge, wie du siehst)

Siehe auch:
[Artikel] MVVM und DataBinding
MVVM observablecollection canvas
Setting Canvas properties in an ItemsControl DataTemplate

Weeks of programming can save you hours of planning

S
SteffoD Themenstarter:in
44 Beiträge seit 2018
vor 5 Jahren

Hi pinki und Co.,

tatsächlich basiert mein Designer auf dem von dir verlinkten. Das Problem ist, dass ich mehrere Tabs habe und teils Elemente in inaktiven Tabs im Hintergrund geladen werden, die noch nicht im Vordergrund sind, weswegen das Problem auftaucht, dass mir die Größe der Elemente noch nicht zur Verfügung steht.

S
SteffoD Themenstarter:in
44 Beiträge seit 2018
vor 5 Jahren

Hallo an alle,

ich habe mich dem Problem nochmal gewidmet und füge nun das imageItemControl erst hinzu, wenn der Tab geladen wird.

public AssayAreaControl(TabItem imageProcessingMethodTab)
{
	Loaded += OnWindowLoaded;
}

private void OnWindowLoaded(object sender, RoutedEventArgs e)
{
	draw();
}

private void draw()
{
	var imageItemDictionary = new ResourceDictionary
	{
		Source = new Uri("/DiagramDesigner;component/Stencils/ImageItemStyle.xaml",
						 UriKind.RelativeOrAbsolute)
	};

	var imageItemStyle = (Style)imageItemDictionary["ImageItemStyle"];

	ImageItem imageItemControl = new ImageItem
	{
		Style = imageItemStyle,
		Image = image,
		Caption = id,
	};

	// imageItemControl.Width --> NaN
	// imageItemControl.Height --> NaN

	// imageItemControl.ActualWidth --> 0
	// imageItemControl.ActualHeight --> 0
}

Leider stehen mir die Dimensionen des Controls immer noch nicht zur Verfügung. Es liegt vielleicht da dran, dass das Control noch nicht hinzugefügt und gezeichnet wurde.
Bleibt mir nun noch die Möglichkeit, mir die Dimensionen über den Style zusammen zu rechnen. Aber ich gelange wie gesagt nicht an die Höhe des TextBlocks. Wie das mit TemplateParts funktionieren soll, habe ich leider immer noch nicht richtig verstanden...

Weiß jemand weiter?

5.658 Beiträge seit 2006
vor 5 Jahren

Weiß jemand weiter?

Wenn ich das richtig sehe, dann bist du keinen Schritt weiter gekommen, und hast alle Antworten ignorieriert, die du auf deine Frage bekommen hast. Solange du einen Style im Code-Behind aus einem ResourceDictionary extrahieren mußt, und solange du UI-Elemente im Code-Behind erstellst, machst du irgendetwas verkehrt. Benutze eine Canvas, dann kannst du Elemente frei positionieren, zoomen und scrollen, und mußt nichts im Code-Behind abmessen.

Weeks of programming can save you hours of planning

S
SteffoD Themenstarter:in
44 Beiträge seit 2018
vor 5 Jahren

Hi MrSparkle,

das ist ein ContentControl, das auf einem Canvas abgelegt wird. Wie gesagt basiert das Programm auf das von pinki verlinkte Tutorial.

Das Problem ist, dass die Elemente dynamisch hinzu kommen und die Objekte (Bilder) eine _beliebige _ Größe haben können, weswegen ich dachte, dass ich auf teilprogrammatische Anweisungen angewiesen bin. Liege ich da falsch?

5.658 Beiträge seit 2006
vor 5 Jahren

Wenn du Objekte auf einer Canvas positionierst, dann verwendest du die Angehängten Eigenschaften Left, Top, Right und Bottom. Du kannst dann die entsprechenden Eigenschaften aus deinem ViewModel daran binden.

Woher die Werte in deinem ViewModel kommen, hängt davon ab, wie die Objekte auf der Canvas positioniert werden sollen: Automatisch oder durch Benutzerinteraktion. Das ist dann die Logik deiner Benutzeroberfläche, und die sollte aussschließlich im ViewModel implementiert sein.

Wenn du ein Bild 1:1 in Originalgröße auf der Canvas anzeigen lassen willst, dann kannst du auch direkt an die Width- und Height-Eigenschaften des Bildes binden.

Also kurz gesagt: Ich sehe keinen Grund, diese Logik im Code-Behind zu implementieren. Ich kenne den Code des NetworkView-Projektes nicht, aber soweit ich beim Überfliegen des Tutorials gesehen habe, wird auch dort MVVM verwendet. Meiner Meinung nach hast du etwas bei der Verwendung von WPF noch nicht verstanden, wenn im Code-Behind Styles aus ResourceDictionarys extrahierst. Styles sollten ausschließlich in XAML zugewiesen werden.

Weeks of programming can save you hours of planning

S
SteffoD Themenstarter:in
44 Beiträge seit 2018
vor 5 Jahren

Hi MrSparkle,

ok, danke. Das werde ich mir wohl am WE anschauen. 🙂