Binding ListView to ObservableCollection of Bitmaps - wpf

I have some problems with binding my ListView to ObservableCollection<Bitmap>...
It's my XAML:
<ListView ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Disabled"
ItemsSource="{Binding Path=FrameImages}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel
Width="Auto"
ItemWidth="{Binding (ListView.View).ItemWidth, RelativeSource={RelativeSource AncestorType=ListView}}"
ItemHeight="{Binding (ListView.View).ItemHeight, RelativeSource={RelativeSource AncestorType=ListView}}" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Border Width="100" Height="75" BorderThickness="1" BorderBrush="DarkGray" VerticalAlignment="Center" Margin="7,5,7,5">
<Image Margin="5,5,5,5" Width="100" Height="75" Source="{Binding}" Stretch="Fill"></Image>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code behind:
public ObservableCollection<Bitmap> FrameImages { get; set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
FrameImages = new ObservableCollection<Bitmap>();
Bitmap img = new Bitmap(#"E:\test\3047817.bmp");
FrameImages.Add(img);
}
When I add the element, it seems to appear in ListView, but it's transparent (just an empty frame):/ I tried to save the bitmap back into file and there were no problems (same as the original one). I don't know why it's not working:(
[edit]
Btw. my code works if I replace ObservableCollection<Bitmap> with ObservableCollection<BitmapSource>. But here's the additional conversion which affects on program's performance... That's why I need Bitmap.

You can't use System.Drawing.Bitmap that way in a WPF application. The class does not belong to WPF. It encapsulates a GDI+ bitmap, whereas WPF is based in DirectX. Hence you need to use BitmapSource.
You might however simply bind the ItemsSource property of your ListView to a collection of image path strings. The necessary conversion from string to ImageSource is performed automatically by WPF.
If you really need to manually create the bitmaps, you should define your collection as ObservableCollection<ImageSource> and create collection elements by something like this:
var img = new BitmapImage(new Uri(#"E:\test\3047817.bmp"));
FrameImages.Add(img);
You might want to take a look into Imaging Overview. And you should make yourself familiar with the BitmapSource class hierarchy in WPF.

Related

Using WPF/XAML to add dynamic controls to a form

I'm new to XAML/Wpf forms so maybe this project is a little outside of my ability. I would like to make a scheduler that essentially adds a bunch of "Jobs" to the form, each one has it's own abilities like click events and such but for the most part they are labels with a background and a set size.
Here is something like what I would like that I had made (with data removed) in Winforms, but it was very slow to add to the form. I was hoping some sort of databinding and user controls with xaml would help me out.
There will be a large number of these jobs, ideally I was thinking each row could be it's own usercontrol or each section (marked here as assembly cell).
I had some code that was able to place jobs onto the window, but there was no user control and no row or section labels. It looked like this in the xaml:
<Window.Resources>
<DataTemplate x:Key="Job">
<Label Content="{Binding}" Height ="100" Width="100" BorderThickness="1" BorderBrush="Black"/>
</DataTemplate>
<DataTemplate x:Key="wrkCenterPanel">
<ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource job}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" Width="Auto" Height="Auto" >
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</Window.Resources>
...
<Label Grid.Row="0" Grid.Column="0" Content="Production Scheduler" Width="Auto" Height="Auto" FontSize="40" FontWeight="Bold" HorizontalAlignment="Center" />
<ScrollViewer Grid.Row="1" Grid.Column="0" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="sched" ItemTemplate="{DynamicResource wrkCenterPanel}" />
</ScrollViewer>
In the code I just had a List(Of List(Of String)) And I added each row as a new list of strings and then binded it to sched. Is this even the right direction to be moving in? Any help at all would be greatly appreciated.
You do seem to be heading in the right direction, however I would suggest reading up on MVVM before you continue so that you can truly understand how DataBinding and the WPF template system will be of benefit. The Prism documentation on MVVM is a good place to start, but there are countless other places to learn about MVVM with a simple search.
The jist of it is that your DataTemplates are a good start, however you can go further. Rather than just using Strings and lists of strings, you can use CLR Objects (ViewModels, in MVVM terminology) and then define DataTemplates (Views) for visually representing those CLR Objects.
The result is that your XAML will look something like this (incomplete, but demonstrative):
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type vms:JobViewModel}">
<Label Content="{Binding Name}" Height ="100" Width="100" BorderThickness="1" BorderBrush="Black"/>
</DataTemplate>
<DataTemplate DataType="{x:Type WorkCenterViewModel}">
<ItemsControl ItemsSource="{Binding Jobs}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" Width="Auto" Height="Auto" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</Window.Resources>
<ContentPresenter Content="{Binding WorkCenter}"/>
</Window>
And your CLR Objects will look something like this:
public class JobViewModel : ViewModel
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public class WorkCenterViewModel : ViewModel
{
private ObservableCollection<JobViewModel> _jobs;
public ObservableCollection<JobViewModel> Jobs
{
get { return _jobs; }
}
}
When you define your DataTemplates without the x:Key property, but with a DataType property instead, it will automatically apply to any instances of that type it finds.

How can I bind to a parent UIElement within an ItemTemplate?

I'm trying to bind to a property of MainWindow, but from a ContextMenu within a DataTemplate. How can I achieve this?
I can't use ElementName, as the contextMenu isn't part of the visual tree
I can't use PlacementTarget, as this will give the UIElement produced by the DataTemplate
<Window x:Class="WpfApplication24.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ItemsControl ItemsSource="{Binding Data}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Padding="5" CornerRadius="10" BorderThickness="1" BorderBrush="Red">
<Border.ContextMenu>
<ContextMenu ItemsSource="{Binding <I want to bind to a property of MainWindow here>}"/>
</Border.ContextMenu>
<TextBlock Text="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can have the window object in Tag of your Border and then can access it using PlacementTarget.Tag
<DataTemplate>
<Border Padding="5" CornerRadius="10" BorderThickness="1" BorderBrush="Red"
Tag="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType=Window}}">
<Border.ContextMenu>
<ContextMenu ItemsSource="{Binding PlacementTarget.Tag.PropertyName,
RelativeSource={RelativeSource Self}}"/>
</Border.ContextMenu>
<TextBlock Text="{Binding}"/>
</Border>
</DataTemplate>
What I used is a simple custom control wrapper e.g. MyContextMenu
...with just one line of code, something like...
public class MyContextMenu : ContextMenu
{
public override void EndInit()
{
base.EndInit();
NameScope.SetNameScope(this, NameScope.GetNameScope(App.Current.MainWindow));
}
}
...and use that instead of ContextMenu.
That always 'scopes' to the MainWindow which may not always be optimal - but you can use ElementName etc.
2) The other option is using NameScope.NameScope="{StaticResource myNameScope}"
NameScope.NameScope seems like an optimal solution - however, you cannot bind from it (and it binds 'too late').
But you can use {StaticResource ...} - and you make a class which wraps around MainWindow's scope.
Similar, but I found the above 'less disruptive' (you can pretty much write the code you'd normally write).
For more details take a look at this answers (and for more ideas)...
ElementName Binding from MenuItem in ContextMenu
How to access a control from a ContextMenu menuitem via the visual tree?

ItemsControl button click command

I need some quick help which is a road blocker for me now. I have Button in ItemsControl and I need to perform some task on Button click. I tried adding Command to Button in ItemsControl DataTemplate but its not working. Can anyone suggest how to proceed further.
<UserControl.Resources>
<DataTemplate x:key="mytask">
<TextBox Grid.Row="5" Grid.Column="2" Text="{Binding Path=PriorNote}" Grid.ColumnSpan="7" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,5" Width="505" Foreground="Black"/>
<StatusBarItem Grid.Row="2" Grid.Column="8" Margin="8,7,7,8" Grid.RowSpan="2">
<Button x:Name="DetailsButton" Command="{Binding CommandDetailsButtonClick}">
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl Grid.Row="1"
ItemsSource="{Binding ListStpRules}"
ItemTemplate="{StaticResource myTaskTemplate}" Background="Black"
AlternationCount="2" >
</ItemsControl>
</Grid>
and in ViewModel I have implemented code for Command. And its not working. Please suggest any solution for me to proceed further
The DataContext of each item in your ItemsControl is the item in the collection the ItemsControl is bound to. If this item contains the Command, your code should work fine.
However, this is not usually the case. Typically there is a ViewModel containing an ObservableCollection of items for the ItemsControl, and the Command to execute. If this is your case, you'll need to change the Source of your binding so it looks for the command in ItemsControl.DataContext, not ItemsControl.Item[X]
<Button Command="{Binding
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}},
Path=DataContext.MyCommand}" />
If your ViewModel has a property of type ICommand you can bind the Button's Command property to that:
XAML:
<DataTemplate DataType="{x:Type my:FooViewModel}">
<Button Content="Click!" Command="{Binding Path=DoBarCommand}" />
</DataTemplate>
C#:
public sealed class FooViewModel
{
public ICommand DoBarCommand
{
get;
private set;
}
//...
public FooViewModel()
{
this.DoBarCommand = new DelegateCommand(this.CanDoBar, this.DoBar);
}
}
Read this:
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
Implement a class similar to RelayCommand in the above article. Would make your further MVVM coding easier. :-)
Just a guess. Is CommandDetailsButtonClick defined in a ViewModel, which is DataContext of your UserControl (the one with ListStpRules property)?
DataContext of button in ItemTemplate is an item from ListStpRules, and if you command is not there then binding won't find it.
You can check diagnostic messages from wpf in Output window while debugging your application. It writes there if it can not resolve binding.

ListBox.ItemsSource binding in code and in xaml

I wrote simple code like
public ObservableCollection<string> Names …
public Window1()
{
PutInDataIntoNames();
InitializeComponent();
this.listBox1.ItemsSource = Names;
}
and in xaml
<Grid>
<ListBox Margin="10,11,10,16"
Name="listBox1"
Background="Black"
Foreground="Orange"
/>
</Grid>
Then I wanted to set ItemsSource property in xaml. In order to do that I wrote the following:
ItemsSource="{Binding Path=Names}"
Unfortunately, it doesn’t work. Could you explain why and how to do that right?
If you only specify the binding path the binding engine will try to navigate the path starting from the current DataContext so ItemsSource="{Binding Path=Names}" does not work like this, there are a lot of different things to keep in mind especially when doing more complex things.
The single most important article that everyone who is new to DataBinding should read is the Data Binding Overview on MSDN
To get back to your binding, if you want to do it completely in XAML you can do that as well, you just need to make the Window your source somehow, either by referencing it directly or relatively or by setting it up as the DataContext.
1 - Direct Reference:
<Window Name="Window"
...>
<Grid>
<ListBox ...
ItemsSource="{Binding ElementName=Window, Path=Names}"
.../>
</Grid>
</Window>
2 - Relative Reference
<Grid>
<ListBox ...
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Names}"
.../>
</Grid>
3 - Setting up the DataContext
<Window ...
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
<Grid>
<ListBox ...
ItemsSource="{Binding Path=Names}"
.../>
</Grid>
</Window>
Do this in code behind
public Window1()
{
PutInDataIntoNames();
InitializeComponent();
DataContext = this;
}
and in XAML
<Grid>
<ListBox ItemsSource="{Binding Names}"
Margin="10,11,10,16"
Name="listBox1"
Background="Black"
Foreground="Orange"
/>
</Grid>
Ideally you should follow MVVM design to isolate data from code behind.
It seems that your Names might be a field. You can ONLY bind to public properties

WPF ListView with horizontal arrangement of items?

I want to lay out items in a ListView in a similar manner to the WinForms ListView in List mode. That is, where items are laid out not just vertically but horizontally in the ListView as well.
I don't mind if the items are laid out like this:
1 4 7
2 5 8
3 6 9
Or like this:
1 2 3
4 5 6
7 8 9
As long as they are presented both vertically and horizontally in order to maximize the use of available space.
The closest I could find was this question:
How do I make WPF ListView items repeat horizontally, like a horizontal scrollbar?
Which only lays out the items only horizontally.
It sounds like what you are looking for is a WrapPannel, which will lay the items out horizontally until there is no more room, and then move to the next line, like this:
(MSDN)
alt text http://i.msdn.microsoft.com/Cc295081.b1c415fb-9a32-4a18-aa0b-308fca994ac9(en-us,Expression.10).png
You also could use a UniformGrid, which will lay the items out in a set number of rows or columns.
The way we get the items to arange using these other panels in a ListView, ListBox, or any form of ItemsControl is by changing the ItemsPanel property. By setting the ItemsPanel you can change it from the default StackPanel that is used by ItemsControls. With the WrapPanel we also should set the widths as shown here.
<ListView>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Width="{Binding (FrameworkElement.ActualWidth),
RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
ItemWidth="{Binding (ListView.View).ItemWidth,
RelativeSource={RelativeSource AncestorType=ListView}}"
MinWidth="{Binding ItemWidth, RelativeSource={RelativeSource Self}}"
ItemHeight="{Binding (ListView.View).ItemHeight,
RelativeSource={RelativeSource AncestorType=ListView}}" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
...
</ListView>
I recently research how to achieve this in WPF and found a good solution. What I wanted was to the replicate the List mode in Windows Explorer, i.e. top-to-bottom, then left-to-right.
Basically what you want to do override the ListBox.ItemsPanel property to use a WrapPanel with it's orientation set to Vertical.
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
However this WILL be slow when loading a large data set as it the wrap panel is not virtualised. This is important. So this task now becomes a little more as now you need to write your own VirtualizedWrapPanel by extending VirtualizedPanel and implementing IScrollInfo.
public class VirtualizedWrapPanel : VirtualizedPanel, IScrollInfo
{
// ...
}
This is as far as I got in my research before having to go off to another task. If you want more information or examples, please comment.
UPDATE. Ben Constable's has a great series on how to implement IScrollInfo.
There are 4 articles in total. A really good read.
I have since implemented a virtualized wrap panel, it is not an easy task even with the help of the above series of articles.
In my case, the best option was to use:
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"
MaxHeight="{Binding (FrameworkElement.ActualHeight), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
ItemWidth="{Binding (ListView.View).ItemWidth, RelativeSource={RelativeSource AncestorType=ListView}}"
MinHeight="{Binding ItemHeight, RelativeSource={RelativeSource Self}}"
ItemHeight="{Binding (ListView.View).ItemHeight, RelativeSource={RelativeSource AncestorType=ListView}}"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
This gave me a decent analog to Windows Explorer's List option
for left to right then top to bottom use
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"
MaxWidth="{Binding ActualWidth, Mode=OneWay,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type er:MainWindow}}}"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
In addition to #Dennis's answer, about the WrapPanel losing Virtualization, I have found a nice class that correctly implements this. While the suggested post by Ben Constable (Part 1, Part 2, Part 3, Part 4) is a nice introduction, I couldn't quite complete the task for a Wrap Panel.
Here is an implementation:
https://virtualwrappanel.codeplex.com/
I've tested it with total of 3.300 video's and photo's, loading the list itself is of course a bit long, but eventually it is correctly virtualizing the list, no scroll lag whatsoever.
There are some issues to this code, see the issues tab on the page above.
After adding the source code to your project, example source code:
<!--in your <Window> or <UserControl> tag -->
<UserControl
xmlns:hw="clr-namespace:Project.Namespace.ToClassFile" >
<!--...-->
<ListView x:Name="lvImages" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Margin="10" Height="auto"
ItemsSource="{Binding ListImages}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<hw:VirtualizingWrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="5" MaxHeight="150">
<TextBlock Text="{Binding title}" FontWeight="Bold"/>
<Image Source="{Binding path, IsAsync=True}" Height="100"/>
<TextBlock Text="{Binding createDate, StringFormat=dd-MM-yyyy}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
MVVM style back-end, so this is inside the ViewModel:
public ObservableCollection<Media> ListImages
{
get
{
return listImages;
}
set { listImages = value; OnPropertyChanged(); }
}
//Just load the images however you do it, then assign it to above list.
//Below is the class defined that I have used.
public class Media
{
private static int nextMediaId = 1;
public int mediaId { get; }
public string title { get; set; }
public string path { get; set; }
public DateTime createDate { get; set; }
public bool isSelected { get; set; }
public Media()
{
mediaId = nextMediaId;
nextMediaId++;
}
}

Resources