Drawing a seat area in wpf - wpf

New to WPF I am after some advice, pointers-articles-codesnippets that can help me to write a usercontrol containing a seating area.
Lets say a cinema has 200 seats and it a square.
I need to be able to draw 200 seats.
What is the best way to draw seats? Would you draw 200 rectangles?

This will be done best with an ItemsControl, and yes, I would render each seat as a Rectangle so you can get all the mouse and selection events (since I imagine you want users to be able to select seats). If you want less overhead, you could step down to using rectangle geometries, but for 200 seats that are not moving, the overhead of a Rectangle will not be bad.
First, I would create some code-behind to store information about each seat. I am not sure what seat data you want in your model, but I think at the very least you would like to see a seat number. You could add other data such as an occupied or reserved status for the seat, but for now I have kept it simple:
public partial class SeatingArea : UserControl
{
public ObservableCollection<int> Seats { get; private set; }
public SeatingArea()
{
Seats = new ObservableCollection<int>();
for (int i = 1; i <= 200; i++)
Seats.Add(i);
InitializeComponent();
}
}
Now for the XAML, you need to create an ItemsControl and set its ItemsSource to the seats collection. Then, using the ItemTemplate property, you can control how each seat will be rendered. In this case, it is simple: we will draw a single rectangle and overlay some text containing the number on top of the rectangle. Finally, we need the seats to be arranged in a square, so we set our ItemsPanel property to be a WrapPanel. This ensures that the seats will be arranged in rows. For the finishing touches, I have added a Trigger that will give seats a red glow when they are moused over. You can imagine many other triggers that would not be difficult to add.
<UserControl x:Class="TestWpfApplication.SeatingArea"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="Beige"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<TextBlock HorizontalAlignment="Center" Text="SEATING CHART" FontSize="24" Margin="0,10"/>
<ItemsControl ItemsSource="{Binding Seats}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Rectangle x:Name="Seat" Width="25" Height="25" Stroke="Black" Fill="Green" Margin="1,2"/>
<TextBlock Text="{Binding}" TextAlignment="Center" VerticalAlignment="Center"
Foreground="White" FontWeight="Bold"/>
</Grid>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Seat" Property="Effect">
<Setter.Value>
<DropShadowEffect Color="Red" ShadowDepth="0"/>
</Setter.Value>
</Setter>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Height="300" Width="550"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
With all of this, here is the resulting (super simple) seating chart:
alt text http://img62.imageshack.us/img62/2944/seatingchartcontrol.png

Related

WPF listbox with canvas only showing last item

I have a ListBox with a Canvas as ItemsPanel.
<UserControl.Resources>
<DataTemplate x:Key="itemTemplate">
<Border BorderBrush="LightBlue" BorderThickness="1">
<Grid Margin="0,2,2,2" Width="{Binding Width}" Height="{Binding Height}">
<Rectangle Cursor="Hand" Fill="AliceBlue"
MouseDown="Rectangle_MouseDown"
MouseMove="Rectangle_MouseMove"
MouseUp="Rectangle_MouseUp"/>
<Label Content="{Binding Name}" Margin="5" IsHitTestVisible="False"/>
</Grid>
</Border>
</DataTemplate>
</UserControl.Resources>
<ListBox ItemsSource="{Binding Items}"
x:Name="listBox"
SelectionMode="Extended"
ItemTemplate="{StaticResource itemTemplate}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
The problem is that whenever I add a new item to Items, which Listbox is binding to, it only shows that new item on screen. All previous items in the list are not shown. I can see that all the items are indeed in the Items list, and ListBoxItems are added to the visual tree. But I cannot see them. Only the last item added.
This is what it looks like running (only ever one item is shown)
This is what it looks like in designer and how it should look like running
Any suggestions?
UPDATE 1
The code the designer uses is this
public class DrawingPanelViewModelMockup: DrawingPanelViewModel
{
public DrawingPanelViewModelMockup()
{
//Pc subclasses DrawingComponent
var pc = new Pc();
pc.Name = "PC";
pc.X = 20;
pc.Y = 40;
pc.Width = 100;
pc.Height = 50;
Items.Add(pc);
...
}
}
And the real code that adds to Items (ObservableCollection) is this. It's part of a Drag-n-drop operation.
var comp = e.Data.GetData(typeof(DrawingComponent).FullName) as DrawingComponent;
var drawingPanelVm = ServiceLocator.Current.GetInstance<DrawingPanelViewModel>();
comp.X = mousePos.X;
comp.Y = mousePos.Y;
comp.Width = 100;
comp.Height = 50;
drawingPanelVm.Items.Add(comp);
The XAML works fine, and you've confirmed that there's only one copy of the viewmodel created, hence only one Items collection, and the first drop works.
Looking at your code, what jumps out at me is this line:
var comp = e.Data.GetData(typeof(DrawingComponent).FullName) as DrawingComponent;
That's not creating a DrawingComponent; it's pulling one out of a hat that something else put it into. I'd put a breakpoint in there and see if you're actually getting multiple items in Items, but they're all the same actual object instance, with the same property values.
Or I'd just go straight to the code that starts the drag, and make sure you're creating a new DrawingComponent every time -- or else create a clone each time on the drop end. Doing it on the drag end seems better though, because then you can drag different subclasses of DrawingComponent from different sources and the drop code doesn't need to worry about it.

Zero-height of ScrollViewer-templated item inside ItemsControl?

I have an ItemsControl with arbitrary items. Some of the items are wrapped inside a ScrollViewer. The code-behind for these scrollable items makes use of the ViewportWidth (almost equivalent to ActualWidth) and ViewportHeight (almost equivalent to ActualHeight) properties to arrange/size its visual children. This works as long as I don't put the item inside an ItemsControl. When the item appears in an ItemsControl the value of ViewportHeight equals 0 - effectively making my item invisible. Note that I want to arrange the items vertically, giving all items equal height! No fancy stuff, just a regular StackPanel.
The templates are applied automatically using DataType:
<MyControl.Resources>
<DataTemplate DataType="{x:Type MyScrollableItem}">
<MyControlWrappedInScrollViewer Text="{Binding Text}" />
</DataTemplate>
<DataTemplate DataType="{x:Type MyItem}">
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</MyControl.Resources>
<ItemsControl ItemsSource="{Binding MyCollection}" />
The structure of MyControlWrappedInScrollViewer looks something like this:
<UserControl>
<Grid>
<ScrollViewer CanContentScroll="True">
<Canvas />
</ScrollViewer>
</Grid>
</UserControl>
Why does my ScrollViewer get the height of 0? How can I tell my ItemsControl to size the item appropriately? E.g. One item yields a height of the ItemsControl height. Two items yield half of it, and so on.
This did the trick:
<Style TargetType="{x:Type ItemsControl}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid Columns="1" IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>

wfp charting toolkit: show data point values above all columns

total emergency here. Just saw WPF for the first time and need this quick, so forgive me: if I don't provide enough info first time around I promise to edit the question.
In a charting object, defined with namespace:
xmlns:charting="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
I am drawing a simple bar chart.
<charting:Chart Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3"
Visibility="{Binding Path=MyCurrentResultsView, Converter={StaticResource ResourceKey=NullObjectToVisibilityConverter}}"
Background="Transparent" Foreground="White"
Margin="50,0,50,0" Height="350"
HorizontalAlignment="Stretch" Title="{Binding Path=MyCurrentResultsView.Name}">
<charting:ColumnSeries Height="350" Foreground="Black"
ItemsSource="{Binding Path=MyCurrentResultsView.ResultsView}"
IndependentValueBinding="{Binding Key}"
DependentValueBinding="{Binding Value}">
</charting:ColumnSeries>
</charting:Chart>
What I'd like to do is to show the value of each column above the column (or even inside the column rectangle if possible: these are percentage values and the idea is to make them more visible on the bar chart).
I have been looking at the styling information, but here this is more than just style. I see two possibilities. Either:
For each column item in the series, define a transformation that positions a frame above each column, creates a text box whose label is set to the dependent value, then draws the text box inside the frame.
Find some kind of property on "ColumnSeries" or "? ColumnItem ?" that "enables" the display of the bound value above the column.
Total shot in the dark here. Thanks.
I would try to change the ColumnDatapointTemplate like this:
<charting:ColumnSeries Height="350" Foreground="Black"
ItemsSource="{Binding Path=MyCurrentResultsView.ResultsView}"
IndependentValueBinding="{Binding Key}"
DependentValueBinding="{Binding Value}">
<charting:ColumnSeries.DataPointStyle>
<Style TargetType="charting:ColumnDataPoint">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="charting:ColumnDataPoint">
<Grid>
<Rectangle Fill="{TemplateBinding Background}" Stroke="Black"/>
<Grid Margin="0 -20 0 0" HorizontalAlignment="Center" VerticalAlignment="Top">
<TextBlock Text="{TemplateBinding FormattedDependentValue}" Margin="2"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</charting:ColumnSeries.DataPointStyle>
</charting:ColumnSeries>
Play a bit with vertical alignments and/or margins and you will be able to get infos into the columns and other.
Hope this help!

ListView for timetable

I'm developing an app that presents a timetable, that looks like this:
However, I have several problems with it:
When there are too many items (minutes) to display in 1 row it
should be broken automatically in several rows (e.g. all rows after
06 should be wrapped)
I don't know where the spacing around minute items comes from. It's not
item`s margin.
When scrolling the timetable list with finger, it only gets scrolled if no minute box is touched. Otherwise the minute box moves a bit, not the
entire timetable list.
The timetable list is bound to an ObservableCollection of TimetableHour instaces:
public class TimetableHour
{
public sbyte Hour { get; set; }
public IList<TimetableItem> Items { get; set; }
public string HourString
{
get { return Hour.ToString("00") + ":"; }
}
}
and the XAML page:
<Style TargetType="ListView" x:Key="TimetableListViewStyle">
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden" />
<Setter Property="Margin" Value="0,0,60,0" />
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding HourString}"
Width="60" Height="50"
TextAlignment="Center"
Background="CornflowerBlue" Foreground="White" BorderThickness="0"
FontSize="23"
Padding="10">
</TextBox>
<GridView ItemsSource="{Binding Items}" Height="Auto" SelectionMode="None" IsTapEnabled="False" IsHoldingEnabled="False" >
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Horizontal" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel Background="LightSkyBlue" Width="60" Height="50">
<TextBlock Text="{Binding Minute}" HorizontalAlignment="Center" FontSize="17" FontWeight="Medium" Margin="0,2,0,0"></TextBlock>
<TextBlock HorizontalAlignment="Center">Tip</TextBlock>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<ListView Grid.Row="2" Grid.Column="2" ItemsSource="{Binding Timetable}"
Style="{StaticResource TimetableListViewStyle}"
SelectionMode="None" />
Ok, so I figured them out:
To make the minutes items wrap I needed to specify the exact width
of the GridView that is used to display them. Alternatively I can use a Grid instead of a StackPanel, to allow to use all available space - see GridView width inherited from parent
To control the spacing around GridView items you need to define the
ItemContainerStyle as described here:
Windows8 ListView and space between items
To achieve this I can set IsSwipeEnabled="False" on the GridView.

Generic, bindable, uniform-stretch layout

Excuse the length, trying to ensure all info's contained!
I need a view of cells (a view with corresponding VM) almost in a circle like so:
********
**********
************
**************
**************
**************
**************
************
**********
********
A few of the complications:
Before we fire up I don't know how many cells to display. All cells must be equal in size. The entire view must be scalable.
In my simplified world, my window creates 20 RowViewModel objects and passes each constructor the number of columns it should create.
My main view:
<Viewbox Stretch="Uniform">
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},Path=Rows}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding Path=Rows.Count}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Viewbox>
And my RowView:
<Grid>
<ItemsControl ItemsSource="{Binding Columns}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding Columns.Count}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
My cell view:
<Border >
<Viewbox Stretch="Uniform" >
<Label FontSize="10" Content="{Binding Notice}" Foreground="White" />
</Viewbox>
</Border>
As it stands, all rows are the same width so rows with fewer cells have wider cells. If I tell each row there are 20 cells, each cell is the same size but everything aligns left despite HorizontalAlignment settings, presumably because it appends blank cells. I'm assuming I could insert blank cells where I want them but this feels like a fudge of the data to make the display right, which I'm sure you'll agree is B-A-D.
I've tried squillions of approaches and think this is the closest so far but I'm missing it. Can you help please?
Thanks for your patience.
Well, maybe it's impossible? I ended up adding a property to the ViewModel that builds the grid and assigns the appropriate row/column during the generation, then databinding a contentcontrol to that grid.
It remains unsolved - how to do it in XAML?
The view:
<Viewbox Stretch="Uniform">
<ContentControl Content="{Binding PrettyGrid}" />
</Viewbox>
In the ViewModel:
public Grid PrettyGrid
{
get
{
return BuildGrid(data);
}
}
and the BuildGrid snippets:
private static Grid BuildGrid(List<objects> cellData)
{
var localGrid = new Grid();
// ...
localGrid.RowDefintions.Add(...);
localGrid.ColumnDefinitions.Add(...);
cellData.ForEach(cell =>
{
ContentControl ctl = new ContentControl();
ctl.Content = cell;
Grid.SetRow(ctl, cell.Row);
Grid.SetColumn(ctl, cell.Column);
localGrid.Children.Add(ctl);
});
return localGrid;
}

Resources