C# WPF - Array von Controls

Krik

Fleet Admiral Pro
Registriert
Juni 2005
Beiträge
16.938
Moin,


vorne weg, ich habe keine Ahnung von WPF und XAML. Ich blicke nicht durch und finde keinen richtigen Startpunkt.

Ich programmiere gerade eine Spiel, dass Sudoku ähnelt. Dazu brauche für die Anzeige ein Array aus Button Controls. Die Button Controls sollen über eine HotkeyAction (nennt sich das so?) dann Zahlen oder Buchstaben zugeordnet bekommen (also anklicken und dann Taste rücken, fertig).

Mein Problem ist jetzt, dass ich dazu vermutlich ein eigenes Control benötige, dass die Buttons in eine Art Tabelle zusammenfassen soll. Dabei muss es explizit möglich sein, bei einzelnen Buttons die Eigenschaften später im C#-Code zu ändern (zB Hintergrundfarbe und ob sie überhaupt klickbar sind) und abzufragen (welche Zahl eingegeben wurde). Ich weiß gar nicht, wo ich hier anfangen soll.

Ja, es ist eine Art Hausaufgabe, aber es ist noch genügend Zeit da, um mich in die Sache einzuarbeiten. Weiß jemand, wo ich zB ein gutes Tutorial zu so einem Thema her bekomme, damit ich verstehen kann, wie man da überhaupt heran geht? WPF ist ja dermaßen umfangreich, dass man als Anfägner einfach erschlagen wird.


Gruß, Laurin
 
Die Tabelle nennt sich in WPF UniformGrid
Diesem Grid kannst du eine Benutzerdefinerte Anzahl von Spalten und Zeilen geben und dann kannst du da Controls rein machen wir du magst. Zum Beispiel deine Buttons.
 
Jo man wird förmlich erschlagen, aber an sich ist es von der programmiertechnischen Herangehensweise nicht sooooo viel anders als bei den alten Forms. Die Zauberworte bei Google-Suchen heissen "programmatically" und "code-behind"... damit findet man sehr viel, wenn man sich nicht großartig mit XAML befassen will.
Der große Unterschied zu Forms ist die stark hierarchische Struktur. Jede Control ist irgendwo einem Container-Element wie z.B. einem Grid oder einer ViewBox untergeordnet. Ein Grid hat Children, eine ViewBox nur ein einzelnes Child (was aber auch ein Grid sein kann mit mehreren Children). Der Eigenschaft Children fügt man einfach die Controls hinzu, einem Child weist man direkt eine Control zu. Nur so als kurze Einführung.
Ein eigenes UserControl bzw. ein Benutzersteuerungselement würde ich an dieser Stelle auch empfehlen. Das ist garnicht so schwer, denn die UserControl-Basisklasse liefert schon alles an Events und Eigenschaften mit. Das anfangs verwirrende, aber im Nachhinein schöne daran ist: jede UserControl kann ihrerseits so viele Controls enthalten wie sie will, d.h. Grids, ViewBoxen, Rectangles, Textfelder, Labels, Circles, usw. usw.! Damit kann man sich eine UserControl sehr sehr gut anpassen und designen.

Lass dir von XAML keinen Schreck einjagen! Man kann sehr gut auch im Code-Behind Elemente anlegen und verwalten! Ein Array für die Controls brauchst du garnicht mal unbedingt glaub ich... die hast ein Grid namens gridSpielfeld und unter gridSpielfeld.Children kannst du deine UserControls hinzufügen und auch darauf zugreifen.
 
Zuletzt bearbeitet: (Korrektur)
Ah, so funktioniert das also. Ist ja gar nicht so unähnlich zu Javas Swing.

Ich werde mir das mit dem Grid anschauen und die Buttons wohl per Schleife generieren. Ich dachte bis jetzt immer, dass man das per XAML machen muss.
 
Nö, zum Glück nicht muss man nicht alles per XAML machen ;) mein aktuelles WPF-Projekt besteht z.B. nur aus nem Grid für ne Menüleiste und einer Viewbox für die Inhalte. Dieser Viewbox weise ich als Child auch nur UserControls hinzu, die wiederrum aus vielen Elementen bestehen und ihren eigenen Code-Behind selbst mitbringen... alles halb so wild und ich muss auch gestehen, dass mir WPF mittlerweile wahnsinnig gut gefällt :D
Ergänzung ()

Kleiner Nachtrag, hier ein Beispiel-Tutorial:
http://arcanecode.com/2007/09/07/adding-wpf-controls-progrrammatically/

Da wird einfach nur ein neuer Button erzeugt, aber statt nem Button kann man genauso einfach ein eigenes Benutzersteuerelement verwenden. Statt "Button newBtn = new Button();" hat man dann halt "myFirstControl newCtrl = new myFirstControl();". Für die Eigenschaft "Content" muss man nur in seinem UserControl eine Textbox haben, die diese Eigenschaft auch anzeigt - sonst sieht man's halt einfach nicht ;) Ob man diese Control nun als Child wie hier zu nem StackPanel, nem Grid, ner ViewBox oder irgendwas anderem zuordnet, ist völlig egal...
 
du brauchst doch keinen Array!! OMG...

definiere im Tag deines Buttons einfach die Nummer, die in deinen Array sein sollte. Du kannst auch 2D Arrayähnliches name verwenden, z.B. link oben button wäre button_1x1, mitte Button: button_1x2 usw! Zerlege dann den Tag mit "_" und dann mit "x"... Fertig sind deine Koordianen. Beim 1D Array musst du nur durchnummerieren und NUR 1x zerlegen!
 
roker002 schrieb:
du brauchst doch keinen Array!! OMG...

definiere im Tag deines Buttons einfach die Nummer, die in deinen Array sein sollte. Du kannst auch 2D Arrayähnliches name verwenden, z.B. link oben button wäre button_1x1, mitte Button: button_1x2 usw! Zerlege dann den Tag mit "_" und dann mit "x"... Fertig sind deine Koordianen. Beim 1D Array musst du nur durchnummerieren und NUR 1x zerlegen!

Was bitte is das für ein Unsinn? Warum sollte ich Buttons irgendwelche Kryptischen Namen geben um diese Namen später zu zerlegen nur um die Grid zu positionieren? Ein Uniformgrid übernimmt doch die ganze ARbeit.
 
Das mit dem Grid hat super geklappt. Allmählich steige ich durch.
Allerdings stehe ich nun vor einem neuen Problem:

Ich habe im Grid ein Border Control, in das ich ein Label gepackt habe:
Code:
<Border BorderThickness="2" CornerRadius="2" Margin="-1" >
	<Label Content="1" Width="30" Height="30" HorizontalAlignment="Center" VerticalAlignment="Center" MouseLeftButtonUp="Label_MouseLeftButtonUp" LostFocus="Label_LostFocus" KeyDown="Label_KeyDown"/>
</Border>
Dazu habe ich diese Eventhandler geschrieben:
Code:
private void Label_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
	if (sender is Label)
	{
		Label control = sender as Label; // sender in das Label casten, dass das Event ausgelöst hat.
		Border Parent = control.Parent as Border; // Rahmen des Labels holen.
		Parent.BorderBrush = Brushes.Red; // Rahmen rot färben.
		Parent.SetValue(Panel.ZIndexProperty, 1); /* Rahmen als oberstes zu zeichnendes Control festlegen,
								damit er nicht von anderen Controls halb überzeichnet wird.*/
	}	
}

private void Label_LostFocus(object sender, System.Windows.RoutedEventArgs e)
{
	if (sender is Label)
	{
		Label control = sender as Label; // sender in das Label casten, dass das Event ausgelöst hat.
		MessageBox.Show("Focus Lost");
		Border Parent = control.Parent as Border; // Rahmen des Labels holen.
		Parent.BorderBrush = Brushes.Black; // Rahmen schwarz färben.
		Parent.SetValue(Panel.ZIndexProperty, 0); /* Rahmen nicht mehr als oberstes zu zeichnendes Control festlegen.*/
	}	
}

private void Label_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
	if (sender is Label)
	{
		Label control = sender as Label; // sender in das Label casten, dass das Event ausgelöst hat.
		control.Content = e.Key;
		MessageBox.Show(e.Key.ToString());
	}	
}

Was will ich haben?
- Wenn ich auf ein Label klicke, soll der Rahmen rot gefärbt werden. Das funktioniert auch.

- Wenn das Label den Focus verliert (also auf ein anderes Control geklickt wurde), soll der Rahmen schwarz gefärbt werden. Das funktioniert nicht!
Das Event wird nie ausgelöst. Keine Ahnung warum. Die MessageBox ploppt einfach nicht auf.
- Wenn das Label ein KeyDown-Event empfängt, soll die gedrückte Taste im Label erscheinen. Das geht auch nicht.
Hier ist es dasselbe Spielchen wie beim LostFocus-Event. Das Event wird offenbar nie ausgelöst.

Hat jemand eine Idee, was ich hier falsch gemacht habe?


Gruß, Laurin
 
Ich weiss nicht, ob ein Label überhaupt den Focus haben kann.

Ich würde anstatt eines Labels einen ToggleButton mit einem TextBlock-Element als Content nehmen, welches den Text enthält, den momentan das Label hat.
Dann kannst du über entsprechende Trigger des IsChecked Propertys die Farbe auch ohne spezielle EventHandler ändern.
 
Vergiss den ganzen C# Code und mach es mit XAML ;)

Code:
<Border x:Name="border" BorderThickness="3"  HorizontalAlignment="Center" VerticalAlignment="Center">            
            <Border.BorderBrush>
                <LinearGradientBrush>
                    <GradientStop Color="Black" x:Name="borderBrushGradiantStop"/>
                </LinearGradientBrush>
            </Border.BorderBrush>
            <Border.Triggers>
                <EventTrigger RoutedEvent="Border.MouseEnter" >
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <ColorAnimation Storyboard.TargetName="borderBrushGradiantStop"
                                                Storyboard.TargetProperty="Color"
                                                To="Red"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
                <EventTrigger RoutedEvent="Border.MouseLeave" >
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <ColorAnimation Storyboard.TargetName="borderBrushGradiantStop"
                                                Storyboard.TargetProperty="Color"
                                                To="Black" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Border.Triggers>
            <Label >
                Test
            </Label>
        </Border>
 
@toeffi
Sieht sehr schick aus. :daumen: Ich habe das gleich mal übernommen.



Das mit der Tastatureingabe habe ich mittlerweile hinbekommen:
Code:
private void Label_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
	if (sender is Label)
	{
		Label control = sender as Label; // sender in das Label casten, dass das Event ausgelöst hat.
		control.Content = e.Key; // gedrückte Taste in das Label schreiben
	}	
}

private void Label_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
	if (sender is Label)
	{
		Label control = sender as Label; // sender in das Label casten, dass das Event ausgelöst hat.
		control.Focus(); // Dem Label den Focus geben, damit Key Events für das selektierte Label ausgelöst werden.
	}
}
Das Problem war nur, dass ein einfacher Mausklick den Focus nicht auf ein Label legt. Das muss man manuell machen. Ich hab's gleich so gemacht, dass das Label schon dann den Focus bekommt, wenn die Maus darüber schwebt.
 
Ich habe eine neue Schwierigkeit entdeckt.

Man nehme zB das stark verschachtelte Control von toeffi. Wie komme ich an das Label Control heran, dass da drin steckt?

Man kann zwar beim Border Control durch die Children iterieren und bei den Children wieder durch deren Children iterieren, aber das muss doch einfacher gehen!
 
Ok, ich hab vergessen, noch etwas zu erwähnen.
Das Control liegt in einer eigenen XAML-Datei und diese Datei wird so geladen.

Code:
using (FileStream fs = new FileStream("Zelle.xaml", FileMode.Open, FileAccess.Read))
            {
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                // ToDo: Auf das Child (label) zugreifen, um den wert zu ändern.
                Grid gridContent = (Grid) XamlReader.Load(fs);

                gridContent.label geht nicht

                //Spielfeld.Children.Add(gridContent);
            }
Im Objekt gridContent taucht "label" nicht auf (diesen Namen hat das Label).
 
Zuletzt bearbeitet:
Ok da kann ich dir leider nicht weiter helfen, den XamlReader hab ich nie benutzt.
Wieso lädst du das überhaupt so explizit rein?
 
Weil mir das mein Kopf so gesagt hat. ^^


Es müssen einmal zum Start des Programms die ganzen Controls geladen werden. Da ich deinen XAML-Code nicht in C#-Code umwandeln konnte, um die Controls manuell zu erstellen, hab ich eben auf die XAML-Datei zurückgegriffen.
Am Ende sind es 81 (9x9) Controls dieser Art, da kann ich nicht einfach Copy/Paste machen. Ich muss also über eine Schleife alle Controls erstellen. In der Schleife steht der Code da oben.
 
Aber du kannst doch die Xaml Datei im Projekt liegen lassen, am besten in einem Resource Dictionary und dann machst du einen Style für eine Border drauß und packst dann an alle Borders die du brauchst. Da musst du das nich komplex über Schleifen irgendwo reinladen und durchiterieren.
 
Ehrlich gesagt, hab ich keine Ahnung, wovon du da redest.
Ich hab etwas gegoogelt und verstehe gar nicht, was dieses Ressourcen-Ding sein soll.

Ach Mann, was haben die mit WPF bloß getrieben, warum kann man da nicht einfach ein Array aus Controls machen?
Ergänzung ()

Ich bekomme es nicht gebacken. Wie kann ich nachträglich generierten Controls ein bestimmtes vordefiniertes Style verpassen?

Aus der MainWindow.xaml:
Code:
<UniformGrid x:Name="Spielfeld" Rows="9" Columns="9" SnapsToDevicePixels="True" Canvas.Left="3" Canvas.Top="3" Margin="-3" HorizontalAlignment="Center" VerticalAlignment="Center">
	<UniformGrid.Resources>
		<Style x:Key="BorderAnimation" TargetType="{x:Type Border}">
			<Style.Resources>

				<LinearGradientBrush x:Key="BorderGradientBrush">
					<GradientStop Color="Black" x:Name="borderBrushGradiantStop"/>
				</LinearGradientBrush>

				<EventTrigger x:Key="BorderGradientBrushHighlight" RoutedEvent="Border.MouseEnter" >
					<EventTrigger.Actions>
						<BeginStoryboard>
							<Storyboard SpeedRatio="4">
								<ColorAnimation Storyboard.TargetName="borderBrushGradiantStop" Storyboard.TargetProperty="Color" To="Red"/>
							</Storyboard>
						</BeginStoryboard>
					</EventTrigger.Actions>
				</EventTrigger>

				<EventTrigger x:Key="BorderGradientBrushDehighlight" RoutedEvent="Border.MouseLeave" >
					<EventTrigger.Actions>
						<BeginStoryboard>
							<Storyboard SpeedRatio="4">
								<ColorAnimation Storyboard.TargetName="borderBrushGradiantStop" Storyboard.TargetProperty="Color" To="Black" />
							</Storyboard>
						</BeginStoryboard>
					</EventTrigger.Actions>
				</EventTrigger>

			</Style.Resources>
		</Style>
	</UniformGrid.Resources>

(...)
Wenn ich jetzt ein neues Control erstelle, wie sage ich ihm, dass er diesen Style verwenden soll?

Border element = new Border();
element.Style = wie geht's hier weiter?
 
Dein Link hat mir echt weitergeholfen. :)
Hab ihn gleich mal abgespeichert, weil ich ihn bisher bei meinen Recherchen nicht gefunden habe.



Edit:
Irgendwie bekomme ich die Animation nicht vernünftig zum Laufen.
In der MainWindow.xaml steht das:
PHP:
<Window.Resources>
	<Style x:Key="BorderStyle" TargetType="{x:Type Border}">

		<!-- Rahmeneigenschaften -->
		<Setter Property="Border.BorderThickness" Value="2"/>
		<Setter Property="Border.Width" Value="30"/>
		<Setter Property="Border.Height" Value="30"/>
		<Setter Property="Border.CornerRadius" Value="2"/>
		<Setter Property="Border.Margin" Value="-1"/>
		<Setter Property="Border.HorizontalAlignment" Value="Center"/>
		<Setter Property="Border.VerticalAlignment" Value="Center"/>

		<Style.Resources>
			<LinearGradientBrush x:Key="BorderBrush">
				<GradientStop Color="Black" x:Name="borderBrushGradiant"/>
			</LinearGradientBrush>
		</Style.Resources>

		<!-- Rahmenanimation -->
		<Style.Triggers>
			<!-- Wenn die Maus drauf kommt, färbe auf rot -->
			<EventTrigger x:Name="BorderBrushGradientHighlight" RoutedEvent="Border.MouseEnter" >
				<EventTrigger.Actions>
					<BeginStoryboard>
						<Storyboard SpeedRatio="4">
							<ColorAnimation Storyboard.TargetName="BorderBrushborderBrushGradiant" Storyboard.TargetProperty="Color" To="Red"/>
						</Storyboard>
					</BeginStoryboard>
				</EventTrigger.Actions>
			</EventTrigger>

			<!-- Wenn die Maus weg geht, färbe wieder schwarz -->
			<EventTrigger x:Name="BorderBrushGradientDehighlight" RoutedEvent="Border.MouseLeave" >
				<EventTrigger.Actions>
					<BeginStoryboard>
						<Storyboard SpeedRatio="4">
							<ColorAnimation Storyboard.TargetName="borderBrushGradiant" Storyboard.TargetProperty="Color" To="Black" />
						</Storyboard>
					</BeginStoryboard>
				</EventTrigger.Actions>
			</EventTrigger>
		</Style.Triggers>

	</Style>
</Window.Resources>
(...)
Im Code steht das:
PHP:
Border element = new Border();
element.Style = (Style)FindResource("BorderStyle");
            
Label label = new Label();
label.Focusable = true;
label.Content = "Test";
label.KeyDown += new KeyEventHandler(Label_KeyDown);
label.MouseEnter += new MouseEventHandler(Label_MouseEnter);
label.MouseLeave += new MouseEventHandler(Label_MouseLeave);
label.MouseDown += new MouseButtonEventHandler(Label_MouseDown);
element.Child = label;

Spielfeld.Children.Add(element);
Das neue Feld wird erstellt, hat aber keinen sichtbaren Rahmen. Wenn ich die Maus über das neue Feld bewege, meldet Visual Studio eine InvalidOperationException.
System.Windows.Data Error: 40 : BindingExpression path error: 'Color' property not found on 'object' ''Border' (Name='')'. null

Border hat allerdings eine Color-Eigenschaft. Mir ist nicht klar, was hier schief geht. Weiß jemand mehr?



Edit:
Ok, hab's irgendwie hinbekommen. Fragt mich nicht wie. Trial'n'Error 4tw! :D
Sieht sogar richtig gut aus.

Ich bedanke mich bei allen, die mir geholfen haben, so weit zu kommen. :daumen:
 

Anhänge

  • Unbenannt.png
    Unbenannt.png
    20,8 KB · Aufrufe: 316
Zuletzt bearbeitet:
Zurück
Oben