WPF listbox with canvas only showing last item - wpf

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.

Related

ItemsControl Not Creating ContentPresenter

Why isn't my ItemsControl creating a ContentPresenter for each item? I'm guessing this is what's making my items not show up (they're set to visible and in the right spot when I inspect using the Live Visual Tree). I'm basically reusing code that works up above in a different ItemsControl and I haven't been able to find anything while searching Google/Stackoverflow with this issue. I can include view model code but I don't think it's related because I see the appropriate values in the Live Property Explorer and can see each WellContainer is in it's appropriate grid cell.
XAML:
<ItemsControl
Grid.Row="1"
Grid.Column="1"
ItemsSource="{Binding Wells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid
x:Name="m_WellGrid"
Margin="5"
wpf:GridHelpers.RowCount="{Binding RowCount}"
wpf:GridHelpers.ColumnCount="{Binding ColumnCount}">
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter
Property="Grid.Row"
Value="{Binding Path=WellRow}"/>
<Setter
Property="Grid.Column"
Value="{Binding Path=WellCol}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="A"
Margin="4"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Live Visual Tree Inspection:
The ItemsControl is designed to wrap the items in a container only when necessary, that is, when the item is not eligible to be its own container. From your comment we find that WellContainer derives from Control, thus is eligible to be its own container1 and is not wrapped in a ContentPresenter. Unfortunately there's no way to control this behavior directly, but you could subclass ItemsControl and override the ItemsControl.IsItemItsOwnContainerOverride method to modify the default behavior.
1 As we can see in the ItemsControl source code it is enough for the item to be of UIElement type to be eligible to be its own container.

ListViewItem in WrapPanel occupying space when collapsed

I have a ListView using a WrapPanel as its ItemsPanel, and I use ListViewItem directly as content. But when one ListViewItem.Visibility is Collapsed, you can still see the space it's using.
First off, a sample XAML code similar to what I use :
<Grid>
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Hidden" ItemContainerStyle="{DynamicResource ContainerStyle}">
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}" x:Key="ContainerStyle">
<Setter Property="Background" Value="Transparent" />
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.Resources>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel ItemHeight="200" ItemWidth="200"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListViewItem Margin="10" Visibility="Visible">
<Border Background="Red"/>
</ListViewItem>
<ListViewItem Margin="10" Visibility="Visible">
<Border Background="Blue"/>
</ListViewItem>
<ListViewItem Margin="10" Visibility="Visible">
<Border Background="Green"/>
</ListViewItem>
</ListView>
</Grid>
For example, when all items are visible (code above) I have this :
But if I change the first item to make it collapsed as follows
<ListViewItem Margin="10" Visibility="Collapsed">
<Border Background="Red"/>
</ListViewItem>
The result is like this :
What I would expect would be the following :
As such I don't understand why it is acting like this, the Collapsedseems to behave just like Hidden. I'm applying it directly to the item and don't see what else to do .
I've tried different solutions I found, most notably this one about binding to Visibility in the style and this one going more or less in the same direction but without success, same results.
The accepted answer actually does not provide a solution, which is instead delivered in its comment section.
If you set ItemWidth, WrapPanel will reserve the ItemWidth for all the items bound to itself, visible or not.
The workaround here is not to set ItemWidth on the WrapPanel but set the Width on the ItemTemplate.
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel MinWidth="96" />
</DataTemaplate>
</ItemsControl.ItemTemplate>
I think that the issue you see is related to the fact that even collapsed, a ListViewItem is still an Item, and the WrapPanel will detect 3 items, not 2.
A good working solution seems to be overriding the "Arrange" methods of Panel, in a custom Panel.
I'm working on the base of this great class AlignableWrapPanel (inheriting from Panel, not WrapPanel), and I got it working by replacing this :
var child = children[i];
if (child == null) continue;
with this :
var child = children[i];
if (child == null) continue;
if (child.Visibility == Visibility.Collapsed) continue;
The methods are ArrangeOverride, ArrangeLine, and MeasureOverride. ArrangeLineis a bit different, but the line with if(child == null) continue;is pretty easy to spot ; just add the one with Collapsed after that.
That solution should work with any

Map control within Listbox

I am trying to display multiple maps within an Listbox.
<Grid Name="MainGrid">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Visible" >
<WrapPanel Name="wrap" >
<ListBox ItemsSource="{Binding MyList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border Margin="5" MinWidth="500" MinHeight="400" BorderThickness="2" BorderBrush="Black"
Width="200"
Height="200" >
<esri:MapView MouseDown="MapView_MouseDown" MouseUp="MapView_MouseUp" >
<esri:Map >
<esri:ArcGISTiledMapServiceLayer ID="BaseMap" ServiceUri="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"/>
</esri:Map>
</esri:MapView>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</WrapPanel>
</ScrollViewer>
</Grid>
The map does not get displayed. Only the logo "esri" gets displayed. But if I remove the listbox, it works fine. What could be the issue?
I am very sure that there are items in my listbox otherwise "esri" would not have appeared.
I have tried Itemscontrol as well but its the same result.
In WPF, we don't put UI elements into collections, we put data objects into collections. Each data item should contain whichever properties are needed to data bind to the UI control that you actually want to display. So your MyList collection should contain a collection of data objects.
Once you have created a custom class to contain these required properties, you will then need to declare a DataTemplate that defines which UI element(s) to display. (See the Data Templating Overview page on MSDN for further information on this).
<ControlTemplate x:Key="YourCustomControlTemplate">
<!-- Define your UI content here -->
</ControlTemplate>
Once you have declared your custom ControlTemplate for your custom data class, you should then put it into a Style for your data object:
<Style TargetType="{x:Type YourXamlPrefix:YourCustomDataClass}">
<Setter Property="Template" Value="{StaticResource YourCustomControlTemplate}" />
</Style>
Note that I have omitted the x:Key directive on this Style... you can add one, but without one (and as long as this Style has been declared in scope of the UI), the Style will be automatically applied to your data objects in the collection.

not able to load style from resource file in code behind during button creation

Im creating a few buttons in code behind.
List<Button> steps = new List<Button>();
Steps.Add(new Button
{
Style = (System.Windows.Style)Application.Current.TryFindResource("WorkflowButtonStyle")
,Content = StepDescription
, Command = WfCommand
, CommandParameter = workflowToFrom
, Width = 206
});
The style always returns as null.
I even tried to load a resource dictionary.
Application.Current.Resources.MergedDictionaries.Add(Application.LoadComponent(new Uri("/Resources;component/Dictionaries/FormD.xaml", UriKind.Relative)) as ResourceDictionary);
and still doesnt load the style.
The dictionary exists in another project..it works when i reference it in the XAML. But doesnt seem to like it in the code behind. Anybody face the same issue?
List<Button> steps = new List<Button>();
Steps.Add(new Button
{
Content = Description
, Command = WfCommand
, CommandParameter = ToFrom
, Width = 200
});
and I set some values in the resource dictionary shown below-
<Style x:Key="ButtonStyle">
<Setter Property="Button.Margin" Value="4"/>
<Setter Property="Button.MinWidth" Value="75"/>
<Setter Property="Button.HorizontalAlignment" Value="Left"/>
</Style>
and the call for including the buttons generated in the code is-
<ItemsControl ItemsSource="{Binding Steps}" VerticalAlignment="Bottom" Style="{StaticResource ButtonStyle}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel FlowDirection="LeftToRight" HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

Drawing a seat area in 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

Resources