Best way to store multiple controls in an array - wpf

I have a UserControl in WPF with numerous child controls which I would like to index like an array. These child controls are in the same grid control as other child controls which I am not interested in.
I'd like to be able to index these controls in a way similar to:
someControl.Children[3];
With out having to avoid controls which I am not interested in. Here's a sample of what I have:
<Grid x:Name="gCalendar">
// more crap here...
<TextBlock Grid.Row="0" Grid.Column="7" TextBlock.TextAlignment="Center">Blah</TextBlock>
<Internal:DayCalendarTime Grid.Row="1" Grid.Column="0" />
<Internal:DayCalendarCore Grid.Row="1" Grid.Column="1"/>
<Internal:DayCalendarCore Grid.Row="1" Grid.Column="2"/>
<Internal:DayCalendarCore Grid.Row="1" Grid.Column="3"/>
<Internal:DayCalendarCore Grid.Row="1" Grid.Column="4" />
<Internal:DayCalendarCore Grid.Row="1" Grid.Column="5"/>
<Internal:DayCalendarCore Grid.Row="1" Grid.Column="6"/>
<Internal:DayCalendarCore Grid.Row="1" Grid.Column="7"/>
</Grid>
I would like to have an array consisting of just the Internal:DayTimeCore controls by putting some sort of wrapping control around them.
Is this possible, or will I manually have to make the array by looping through all children of the grid and adding the ones that are of the type I am interested in?

Another non-WPF way,
Create a code behind control collection by
foreach(DayCalendarTime control in gCalendar.Children)
{
controlCollection[i++] = control;
}

You can wrap these controls inside another Grid control

Related

wpf - Binding issue ActualWidth

I have the following XAML code:
<Grid Grid.Row="2" Name="grid_StatusBar">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="{Binding ElementName=wrapPanel, Path=ActualWidth}" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<ProgressBar Grid.Column="0" HorizontalAlignment="Stretch" Margin="5,5,5,5" Name="progressBar1" VerticalAlignment="Stretch" />
<WrapPanel Grid.Column="1" Name="wrapPanel" HorizontalAlignment="Right">
<Label Content="1" Height="28" HorizontalAlignment="Right" Margin="0,0,0,0" Name="label_Dataset" VerticalAlignment="Stretch" Visibility="Collapsed" />
<Label Content="/20" Height="28" HorizontalAlignment="Right" Margin="0,0,0,0" Name="label_TotalDatasets" VerticalAlignment="Stretch" Visibility="Collapsed" />
<Label Content="ID:" Height="28" HorizontalAlignment="Right" Margin="0,0,0,0" Name="label_IDText" VerticalAlignment="Stretch" />
<Label Content="no id" Height="28" HorizontalAlignment="Right" Margin="0,0,0,0" Name="labelID" VerticalAlignment="Stretch" />
</WrapPanel>
<Button Grid.Column="2" Name="button_Help" Height="30" Width="30" Content="?" HorizontalAlignment="Right" VerticalAlignment="Stretch" Click="button_Help_Click" >
</Grid>
What i am trying to show the progressbar as wide as possible while changing the visibility of some of the labels and setting different texts (and therefore different lengths/widths.
I have then different functions:
At program start the "no id" text of the labelID Label is replaced with an internal value. The width is updated: OK
With the code running I change the visibility of the first two labels, and the ColumnDefinition.Width is not updated and only the first two labels are shown (because there is not place enough for all the 4 of them to fit in the wrapPanel ActualWidth: ERROR!
If I change the Visibility property from the first two Labels from Collapsed to Visible, from start the visibility of all the Labels are Visible, and all the Labels are shown: OK
From the previous state I change visibility of the first two Labels to Collapsed, the ColumnDefinition.Width is updated and the ProgressBar is as wide as possible and all the text is shown: OK
Could anyone please help me? I do not understand why the width is not updated...
NOTE: The visibility is changed using two buttons in the window running the following code:
label_Dataset.Visibility = System.Windows.Visibility.Visible;
label_TotalDatasets.Visibility = System.Windows.Visibility.Visible;
EDIT: My target is to show the Visible Labels using the minimum space (in one line) in order to have the ProgressBar using the maximum width possible.
It is not possible to change the width of a parent element based on the width of a child. In your case, the column is the parent of the WrapPanel, so the ActualWidth of the WrapPanel is not available until after the grid/column has already been sized.
Even if you wrote code to try and circumvent this, you would still run into an issue, as the measure/layout sequence would become re-entrant. Whenever the size of the column changed, it would re-layout all the child controls, one of which is the WrapPanel. This would cause the ActualWidth of the WrapPanel to be changed -- effectively causing an infinite loop. To prevent this stack overflow scenario, the framework immediately detects any re-entrancy in the layout/measure cycle and throws an exception.
I'm not exactly sure what you are trying to achieve. Why do would you want the Labels in a WrapPanel? Surely you would always want the 4 labels to be on the same line, or at least on two lines.
If this is the case, I would set the width of the second Column to "Auto" and put the labels in another container, i.e. one of the following:
If you want all the labels to wrap, as in a WrapPanel being forced to its minimum width, use:
<StackPanel Grid.Column="1" Orientation="Vertical">
... your labels here ...
</StackPanel>
If you want all the labels to stay on the same line, use:
<StackPanel Grid.Column="1" Orientation="Horizontal">
... your labels here ...
</StackPanel>
If you want two lines use:
<StackPanel Grid.Column="1" Orientation="Vertical">
<StackPanel Orientation="Horizontal">
... 1st 2 labels here ...
</StackPanel>
<StackPanel Orientation="Horizontal">
... 2nd 2 labels here ...
</StackPanel>
</StackPanel>

In WPF 4.0, switching tabs resets usercontrol UI

I have a border that contains a TabControl in a HeaderedContentControl:
<Border Grid.Column="1"
Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}"
Header="Decision Workspaces"
Style="{StaticResource MainHCCStyle}"/>
</Border>
The TabControl is defined in a static resource:
<DataTemplate x:Key="ClosableTabItemTemplate">
<DockPanel Width="120" ToolTip="{Binding Path=DisplayName, Mode=OneTime}">
<Button
Command="{Binding Path=CloseCommand}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
FontFamily="Courier"
FontSize="9"
FontWeight="Bold"
Margin="0,1,0,0"
Padding="0"
VerticalContentAlignment="Bottom"
Width="16" Height="16"
/>
<ContentPresenter
Content="{Binding Path=DisplayName, Mode=OneTime}"
VerticalAlignment="Center"
/>
</DockPanel>
</DataTemplate>
<!--
This template explains how to render the 'Workspace' content area in the main window.
-->
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
</DataTemplate>
The Workspaces property which is bound to the HeaderedContentControl's Content property, has a collection of UserControls, which are rendered in the tabs. This all works fine.
The problem is that when i select a row in a grid in one of the UserControls, switch to a different tab, and then return, the selected row is reset. The same happens if a RowDetails is open - when I switch away and back to the tab, it is collapsed.
Any way around this?
Edit: After looking at the proposed solutions for the TabControl behaviour, I'm wandering if I might ditch it altogether. Any ideas for a UI that will allow me to keep several relatively complex UserControls and switch between them, not loosing the visuals in the process?
Thanks!
This is a common problem with the TabControl. Since it only displays the content of the selected tab. If your tab items are not visuals themselves and are presented with a DataTemplate, then the controls will be created and released as you switch tabs.
There are two solutions to this problem here and here, which attempt to retain the visuals for each tab.

How to grab row and column info from a label inside a grid in WPF?

I have a 3x3 grid that I use as a gameboard for tic tac toe. Each grid has a label that can display "X" "O" or "". Each of these labels contains row and column information for where they are located to. I am trying to get that information from code file. So far I have:
<Label Grid.Row="0" Grid.Column="0" Name="lblZeroxZero" MouseDown="lblZeroxZero_MouseDown" FontSize="72" Padding="5" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Label Grid.Row="0" Grid.Column="1" Name="lblZeroxOne" MouseDown="lblZeroxOne_MouseDown" FontSize="72" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" />
<Label Grid.Row="0" Grid.Column="2" Name="lblZeroxTwo" MouseDown="lblZeroxTwo_MouseDown" VerticalContentAlignment="Center" FontSize="72" HorizontalContentAlignment="Center" />
<Label Grid.Row="1" Grid.Column="0" Name="lblOnexZero" MouseDown="lblOnexZero_MouseDown" HorizontalContentAlignment="Center" FontSize="72" VerticalContentAlignment="Center" />
<Label Grid.Row="1" Grid.Column="1" Name="lblOnexOne" MouseDown="lblOnexOne_MouseDown" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" FontSize="72" />
<Label Grid.Row="1" Grid.Column="2" Name="lblOnexTwo" MouseDown="lblOnexTwo_MouseDown" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" FontSize="72" />
<Label Grid.Row="2" Grid.Column="0" Name="lblTwoxZero" MouseDown="lblTwoxZero_MouseDown" FontSize="72" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" />
<Label Grid.Row="2" Grid.Column="1" Name="lblTwoxOne" MouseDown="lblTwoxOne_MouseDown" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" FontSize="72" />
<Label Grid.Row="2" Grid.Column="2" Name="lblTwoxTwo" MouseDown="lblTwoxTwo_MouseDown" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" FontSize="72">
Those are my labels and in my code file I want to get the Grid.Row and Grid.Column info. When I type lblZeroxZero. the intellisense doesn't bring up any properties that contain the row and column information. Does anyone know how to get this info? Do I have to access it from the datagrid as opposed to the label?
Edit: Some more info
In my codefile I have this method (not complete yet)
private int[] GetLabelPosition(Label lbl)
{
int[] rowColumnInfo = new int[2];
if (lbl.Name == "lblZeroxZero")
{
rowColumnInfo[0] = 0;
rowColumnInfo[1] = 0;
}
else if (lbl.Name == "lblOnexZero")
{
rowColumnInfo[0] = 1;
rowColumnInfo[1] = 0;
}
return rowColumnInfo;
}
Currently, the only way I know how to get the label's row and column info is by looking at its name. I would like to get the Grid.Row and Grid.Column information without having to create a bunch of specific cases for each label name.
Use the Attached Properties GetRow and GetColumn at Grid. Example
int row = Grid.GetRow(lblZeroxZero);
int column = Grid.GetColumn(lblZeroxZero);
Update
In your case, you could do something like
private int[] GetLabelPosition(Label lbl)
{
return new int[] { Grid.GetRow(lbl), Grid.GetColumn(lbl) };
}
As OJ suggested, you should use data binding. This lets you separate the problem of presenting the game on the screen from the problem of managing the game's internal logic.
You'll need to create a Cell class that exposes Row, Column, and Owner properties. The class will need to implement INotifyPropertyChanged so that when the Owner gets changed, it can alert the UI by raising the PropertyChanged event. Finally, the class will implement a SelectCellCommand, which will set the Owner for the cell appropriately when it's executed (and whose CanExecute property will be set to false once the cell's owner has been set).
You'll create a collection that contains nine of these Cells, with Row, Column, and Owner properties set to the appropriate initial values. All of your game logic (e.g. who's turn is it? Has someone won?) will examine this collection.
In the UI, you'll create an ItemsControl that's bound to the collection of Cells. Each item in the control will be a cell in the grid.
You'll set the ItemsPanel to a Grid, which will tell the ItemsControl how to lay its items out. You'll create an ItemContainerStyle that assigns the Grid.Row and Grid.Column attached properties to the items' containers - the ItemsControl creates an ItemsPresenter for each object in the ItemsSource, and the Grid.Row and Grid.Column properties must be set on this object in order for the Grid to see them and know where to put them.
Finally, the control will contain an ItemTemplate that describes how each item should be displayed. I'm using a Button (since you want there to be a command that executes when the user clicks on it) that contains a TextBlock which displays the X or the O in Owner.
It might look something like this:
<ItemsControl ItemsSource="{Binding {StaticResource Cells}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
</Grid>
</ItemsPanelTemplate>
<ItemsPanel.ItemContainerStyle>
<Style TargetType="ItemPresenter">
<Setter Property="Grid.Column" Value="{Binding Column}"/>
<Setter Property="Grid.Row" Value="{Binding Row}"/>
</Style>
</ItemsPanel.ItemContainerStyle>
<ItemsPanel.ItemTemplate>
<DataTemplate DataType="local:Cell">
<Button Command="{Binding SelectCellCommand}" VerticalContentAlignment="Center" HorizontalContentAlignment="Center">
<TextBlock FontSize="72" Source="{Binding Owner}"/>
</Button>
</DataTemplate>
</ItemsPanel.ItemTemplate>
</ItemsControl>
</ItemsControl>
Why go through all of this?
Well, let's look at the problems that your current design has that this doesn't:
If you want to change the layout of the cells in your design, you have nine different controls you need to fix. Here, you just change the DataTemplate for the cells.
There's no need for nine different event handlers. Because the buttons are bound to command objects that are properties of the Cell, you don't need to implement any way of finding out which cell the user just clicked on - the command is the way. Also, you can disable the command (and its button) very simply once the Owner is set.
All of your game logic can look at a simple data structure - a collection of Cell objects - without being concerned about how the UI works.
Finally, and most important, data binding is the core of WPF application development. It's what makes developing WPF applications far easier than it was to implement WinForms applications. If you're learning WPF without learning binding, you're wasting time.
+1 for Meleak's first answer, but I'd also like to suggest that instead of using codebehind, why don't you instead use databinding? It'll greatly simplify your code.

how to extend WPF GridRow Contents into next row without expanding the current row

I'm new to WPF and I'm trying to build a dropdown menu using the expander. Page layout is being handled with a Grid.
The extender sits inside the first row of the grid and I would I would like the contents of the expander to expand over top of the contents of everything below when it's clicked. Unfortunately, right now, the entire row is expanded to accommodate the height of the expanded control.
I tried playing around with the ZIndex of the Expander but it doesn't seem to have any effect. No matter what, the row always expands forcing everything else on the page to move.
<Expander FontSize="18" Name="moduleSelect" Width="100" Header=" Goto -> "
Background="#000033" MouseEnter="moduleSelect_MouseEnter"
MouseLeave="moduleSelect_MouseLeave" Foreground="White"
Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="1" HorizontalAlignment="Left">
<StackPanel>
<Button Name="btnTasks" Width="100" Foreground="White"
Background="#000033">Tasks</Button>
<Button Name="btnNotes" Width="100" Foreground="White"
Background="#000033">Notes</Button>
</StackPanel>
</Expander>
How can I make this expand 'above' the subsequent rows without distorting the grid?
What would happen if you set the Grid.RowSpan of the Expander to 2 (or how ever many rows you'd like it to span when expanded)?
So, for a two-row grid, you'd have something like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" /> <!--set this to the height of the expander's header area-->
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<WhateverGoesInRow2 Grid.Row="1" />
<Expander FontSize="18" Name="moduleSelect" Width="100" Header=" Goto -> "
Background="#000033" MouseEnter="moduleSelect_MouseEnter"
MouseLeave="moduleSelect_MouseLeave" Foreground="White"
Grid.Column="0" Grid.ColumnSpan="3" HorizontalAlignment="Left"
Grid.Row=0 Grid.RowSpan="2">
<StackPanel>
<Button Name="btnTasks" Width="100" Foreground="White" Background="#000033">Tasks</Button>
<Button Name="btnNotes" Width="100" Foreground="White" Background="#000033">Notes</Button>
</StackPanel>
</Expander>
</Grid>
You may need to adjust your RowDefinition section for your particular situation, but if I'm understanding your problem correctly, I think this will work.
You want something that pops up over the grid, not expands within the grid. A ComboBox, say, or - this being a menu, after all - a ContextMenu.
You could also build some combination of a ToggleButton and a Popup, but that's essentially the same thing as a ComboBox with IsEditable turned off.
The built-in drop-down control makes use of a Popup control in its default control template to do a similar thing.

Creating grid of hexagons

I have to do a "grid" like this:
Harmonic table
I'm trying to create a ListView with ItemsSource="List<Note>" where every odd note in the list is moved on the bottom...
Is the ListView the right control?
How can I draw an exact hexagon with faces that is near next object?
EDIT: hexagon drawing is solved... this is the xaml:
<Path d:LayoutOverrides="None"
d:LastTangent="0,0" Stroke="Blue" Fill="Red"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Margin="0" Width="100" Height="100" x:Name="Path"
Stretch="Fill"
Data="M8.660254,0 L17.320508,5 17.320508,15 8.660254,20 0,15 0,5 8.660254,0 z"/>
The container for your notes would be an ItemsControl or a ListBox if you need to select items. Then you give your items a template using ListBox.ItemTemplate where you include your hexagon drawing. You have a nice tutorial on Custom ListBox layout.
At this point, your hexagons are displayed one below the other as a ListBox does by default. To get your special layout, you have to change the ListBox.ItemPanel. Here you have two possibilities:
either you use the Canvas panel that supports absolute positioning. In this case your items must have X and Y properties that you will use to position them.
or you create a custom Panel, probably based on Canvas, that is able to convert your custom coordinate system (for example note name + octave number) into X and Y. A bit more difficult but much more reusable. An example of Custom Panel on CodeProject.
HexGrid: CodeProject article
HexGrid: GitHub repository
The key component of a possible solution is a WPF Panel which can arrange hexagonal elements (Standard Panels operate with rectangular child elements). Take a look my HexGrid project (too large to post here). The cental part of it is a HexGrid (WPF Panel which arranges child elements in a honeycomb pattern). Child elements are represented by HexItems (hexagon-shaped ContentControls). There is also HexList (selector ItemsControl which displays items in HexItem container on a HexGrid panel) which gives hex selection support out-of-box.
example of usage:
<hx:HexList Name="HexColors" Orientation="Vertical"
Grid.Row="1"
Padding="10"
SelectedIndex="0"
Background="{Binding Path=SelectedItem.Background, RelativeSource={RelativeSource Self}}"
RowCount="5" ColumnCount="5">
<hx:HexItem Grid.Row="0" Grid.Column="1" Background="#006699"/>
<hx:HexItem Grid.Row="0" Grid.Column="2" Background="#0033CC"/>
<hx:HexItem Grid.Row="0" Grid.Column="3" Background="#3333FF"/>
<!--...-->
<hx:HexItem Grid.Row="4" Grid.Column="1" Background="#CC9900"/>
<hx:HexItem Grid.Row="4" Grid.Column="2" Background="#FF3300"/>
<hx:HexItem Grid.Row="4" Grid.Column="3" Background="#CC0000"/>
</hx:HexList>

Resources