Get current binding index - wpf

My XAML code will creates Labels in WPF window. How can I make each Label's Content contain its position rendered from ItemSource ?
Currently, the result is: AAA BBB CC ( They are 3 labels )
What I want is: 1 2 3 ( They are 3 label or maybe 0 1 2, because the index base on 0 )
<ItemsControl Name="m_Header">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Margin="-2,0,0,0"
Width="{Binding Path=Columns[0].ActualWidth, ElementName=m_DataGrid}"
Content="{Binding}" FontSize="15" Foreground="#777" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

There is no Index property defined in the ItemsControl class, so there is no way to show that value. However, there is a trick that you can use to get what you want. If your ItemsControl.AlternationCount property is set to a suitably high number, then you can use the ItemsControl.AlternationIndex property to do the same thing for you:
<ItemsControl AlternationCount="999"><!--Set this as high as you need-->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Margin="-2,0,0,0" Width="{Binding Path=Columns[0].ActualWidth,
ElementName=m_DataGrid}" Content="{(ItemsControl.AlternationIndex), RelativeSource={
RelativeSource TemplatedParent}}" FontSize="15" Foreground="#777" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you want the numbers to start at a different number, you can add a Converter to simply add or subtract the relevant value from each number.

Adding to #sheridan answer.
Depending on your underlying datasource you might be able use the source count property to set the AlternationCount
Model
public class DemoViewModel {
public ObservableCollection<string> Products { get; set; }
}
Setup DataContext
public MainWindow() {
InitializeComponent();
var vm = new DemoViewModel();
vm.Products = new ObservableCollection<string> { "elm", "oak", "pine" };
this.DataContext = vm;
}
XAML
<ItemsControl AlternationCount="{Binding Products.Count}"
ItemsSource='{Binding Products}'>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation='Vertical'>
<Label Margin="2" Width='200'
Content="{Binding (ItemsControl.AlternationIndex), RelativeSource={
RelativeSource TemplatedParent}}"
Foreground="#777" />
<TextBlock Text='{Binding}' />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Results

All above code work great :).
How to change Binding Path=Columns[0] to Binding Path=Columns[index_here]
It's in Width="{Binding Path=Columns[0].ActualWidth, ElementName=m_DataGrid}"

Related

How to make WPF listview display Images and Labels dynamically

I'm having quite a difficult time trying to create the UI for a WPF Window. I'm trying to display (dynamically) a bunch of Movie Posters with the name of the movie directly under the image. ItemsSource is assigned to a list of Images via foreach iteration. The Image files themselves may be different sizes, but as shown below I will be setting a uniform size.
Basically, my goal is for it to look something like this:
So far, My code only displays a window with one large horizontal row(?) with the image in the center and no label. Here's my XAML code:
<Window x:Name="TVWindow" x:Class="PACS_Pre_Alpha.TV"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TV" Height="746" Width="1000" ResizeMode="NoResize">
<Grid x:Name="TVGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<ListView x:Name="TvBox" HorizontalAlignment="Left" Height="648" VerticalAlignment="Top" Width="994" Grid.Row="5" Grid.Column="5">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" VerticalAlignment="Stretch">
<Image Source="{Binding ImageData}" HorizontalAlignment="Center" VerticalAlignment="Top" />
<TextBlock Text="{Binding Title}" HorizontalAlignment="Center" VerticalAlignment="Bottom" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
My movies are added with this C# code:
foreach (string tvf in ContentFiles)
{
string ContentTitle = System.IO.Path.GetFileNameWithoutExtension(tvf);
MovieData cnt = new MovieData();
cnt.ImageData = LoadImage(ActualImage);
cnt.Title = ContentTitle;
ContentDataList.Add(cnt);
}
TvBox.ItemsSource = ContentDataList;
Edit: I have changed my XAML Markup as #MarkFeldman suggested, but now nothing appears.
Edit: It currently looks like this:
You're going to provide more info about the data itself i.e. what's it's format, how are you assigning it to the ItemsSource etc. For one thing you're not setting the ItemTemplate, so you might want to look at that first. For example if you have a class containing your movie data that looks like this:
public class MovieData
{
private string _Title;
public string Title
{
get { return this._Title; }
set { this._Title = value; }
}
private BitmapImage _ImageData;
public BitmapImage ImageData
{
get { return this._ImageData; }
set { this._ImageData = value; }
}
}
Then you would display it with something like this:
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" VerticalAlignment="Stretch">
<Image Source="{Binding ImageData}" HorizontalAlignment="Center" VerticalAlignment="Top"/>
<TextBlock Text="{Binding Title}" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
UPDATE:
Sorry, I thought it was obvious that you still needed to use a UniformGrid. Here is what your full XAML should look like:
<ListView x:Name="TvBox" HorizontalAlignment="Stretch" VerticalAlignment="Top">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="5" HorizontalAlignment="Stretch"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Image Source="{Binding ImageData}" HorizontalAlignment="Stretch" VerticalAlignment="Top" Stretch="UniformToFill" />
<TextBlock Text="{Binding Title}" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I've already provided you with the MovieData class, so here's what your Window code should look like:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.TvBox.ItemsSource = new MovieData[]
{
new MovieData{Title="Movie 1", ImageData=LoadImage("image.jpg")},
new MovieData{Title="Movie 2", ImageData=LoadImage("image.jpg")},
new MovieData{Title="Movie 3", ImageData=LoadImage("image.jpg")},
new MovieData{Title="Movie 4", ImageData=LoadImage("image.jpg")},
new MovieData{Title="Movie 5", ImageData=LoadImage("image.jpg")},
new MovieData{Title="Movie 6", ImageData=LoadImage("image.jpg")}
};
}
// for this code image needs to be a project resource
private BitmapImage LoadImage(string filename)
{
return new BitmapImage(new Uri("pack://application:,,,/" + filename));
}
}
In this example I'm assuming there is an image in your project called "image.jpg" which has been set to build action "Resource", if your images come from elsewhere then you'll need to modify the LoadImage code accordingly.
I have done something very similar with UniformGrid I see you did not set the Rows of your UniformGrid. I did this In my Game App. Good approach but difficult to get right. Set an ItemTemplate. And try an ItemsControl Outer Object instead of listview
<ItemsControl IsEnabled="{Binding GameBoardEnabled}"
x:Name="_board"
ItemsSource ="{Binding Board}"
ItemTemplate= "{DynamicResource GamePieceTemplate}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!--<StackPanel/>-->
<UniformGrid
Columns="{Binding Columns}"
Rows ="{Binding Rows}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

What is wrong with the following code, list box items don't get displayed?

I want to display a listbox containng list items, I have the following template but it doesn't work, I also changed List l to ObservableList but still same result.
<Window.Resources>
<DataTemplate x:Key="dataTemplate">
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox x:Name="list1"/>
List<String> l = new List<String>();
public MainWindow()
{
InitializeComponent();
list1.ItemTemplate = (DataTemplate)FindResource("dataTemplate");
l.Add("Hi");
l.Add("there");
list1.Items.Add(l);
}
List<string> does not have a property called Items, so your binding is invalid
To make it work, remove the path Items in the the binding so it binds directly to the list
<DataTemplate x:Key="dataTemplate">
<ListBox ItemsSource="{Binding }">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</DataTemplate>
In your Xaml you're binding to an Observable collection called Items, in your code behind your just adding to the items property of the list.
You need to create an ObservableCollection in your code behind called Items, then add to that collection.

Grid of Checkboxes in WPF

I have a WPF UserControl's datacontext tied to a class like this:
public class CheckBoxGridViewModel
{
public List<List<bool>> Checkboxes {get; set;}
}
I want it to display a grid of checkboxes. I'm assuming I can use an Itemscontrol, but don't know exactly how to do it with a dynamic set of columns for each row.
This question seems to answer mine, except the answer didn't give the example and I can't figure how to write it out.
So the question is, how would I write the xaml to display the checkboxes of the Checkboxes property so that they are lined up in a nice grid?
The outer list would be each row and the inner list would be each column of the row.
It's not clear if you're expecting each inner List to be the same size but if they are you can use a simple setup. Using nested ItemsControls with single row/column UniformGrids will give you an even distribution and automatically handle collections of any size without needing to setup Row and Column Definitions like with Grid:
<ItemsControl ItemsSource="{Binding Checkboxes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsPanelTemplate>
<UniformGrid Rows="1"/>
</ItemsPanelTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
See this question. The answer by Jobi Joy will let you present the 2D list but Binding won't work so you can't edit your values.
To be able to bind the values you can use a helper class like this
public static class BindableListHelper
{
public static List<List<Ref<T>>> GetBindable2DList<T>(List<List<T>> list)
{
List<List<Ref<T>>> refInts = new List<List<Ref<T>>>();
for (int i = 0; i < list.Count; i++)
{
refInts.Add(new List<Ref<T>>());
for (int j = 0; j < list[i].Count; j++)
{
int a = i;
int b = j;
refInts[i].Add(new Ref<T>(() => list[a][b], z => { list[a][b] = z; }));
}
}
return refInts;
}
}
This method uses this Ref class
public class Ref<T>
{
private readonly Func<T> getter;
private readonly Action<T> setter;
public Ref(Func<T> getter, Action<T> setter)
{
this.getter = getter;
this.setter = setter;
}
public T Value { get { return getter(); } set { setter(value); } }
}
Then you can set ItemsSource for the ItemsControl with
itemsControl.ItemsSource = BindableListHelper.GetBindable2DList<bool>(Checkboxes);
and the editing should work
Using the same code as Jobi Joy from the question I linked, you can change the Button in DataTemplate_Level2 to a CheckBox and bind IsChecked for it to Value instead (since it will point to the Ref class otherwise)
<Window.Resources>
<DataTemplate x:Key="DataTemplate_Level2">
<CheckBox IsChecked="{Binding Path=Value}" Height="15" Width="15" Margin="2"/>
</DataTemplate>
<DataTemplate x:Key="DataTemplate_Level1">
<ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource DataTemplate_Level2}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</Window.Resources>
<StackPanel>
<Border HorizontalAlignment="Left" BorderBrush="Black" BorderThickness="2">
<ItemsControl x:Name="itemsControl" ItemTemplate="{DynamicResource DataTemplate_Level1}"/>
</Border>
</StackPanel>
Without setting the Content property for the CheckBox it'll look something like this
You might consider creating a class that exposes Row, Column, and Value properties, and binding to a collection of these. This lets you assign row and column positions using the method of your choice, and layout in a grid is very straightforward (once you understand how ItemsControl uses its ItemsPanel and ItemContainerStyle properties, of course):
<ItemsControl ItemsSource="{Binding Checkboxes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Value, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid DockPanel.Dock="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Grid.Row" Value="{Binding Row}"/>
<Setter Property="Grid.Column" Value="{Binding Column}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
How exactly do you want this grid arranged? This will influence which ItemsControl.ItemsPanel to use. A couple of ideas...
Use a UniformGrid, or the WPF Toolkit WrapPanel.

Silverlight: Last item in a ItemsControl

I have an ItemsControl. For the last item in the ItemsControl I want to hide the TextBox containing the comma. Is there a way to do this using XAML?
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Visibility="{Binding Value, Converter={StaticResource NotEmpty}}">
<TextBlock Text="{Binding QuestionName}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding Answer}"/>
<TextBlock Text=", " />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
How about
<TextBlock Text=", " Visibility="{Binding LastItemVisibility}" />
with in your view model something like
public Visibility LastItemVisibility
{
get { return MyCollection.LastOrDefault() == this ? Visibility.Collapsed : Visibility.Visible; }
}
?
It's annoying it's not easier to solve this with a Converter. In fact if you could bind to the ConverterParameter (which is not possible in Silverlight v4) you could achieve what you want quite easily.
If you don't want to touch your model I think your best bet would be to create a new class derived from ControlControl which sets its own visibility based on the position of a bounditem in the itemssource. It's not the neatest solution in the world, but it keeps the model clean. It would look like this in the ItemsControl
<local:ItemsControlVisibilityHelper ShowIfLast="False" ShowIfFirst="True" ShowIfNotLastOrFirst="True"
ItemsControl="{Binding ElementName=x_ItemsControl}"
BoundItem="{Binding}"
>
<TextBlock Text=", "></TextBlock>
</local:ItemsControlVisibilityHelper>

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