Grid of Checkboxes in WPF - 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.

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>

Showing or hiding a control in WPF template based on bindings

I am new to WPF binding/templating. I have some basic questions about a templated TabControl I have as below :
<TabControl x:Name="tcTabs" ItemsSource="{Binding Rooms, UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" Margin="5" BorderThickness="1" IsSynchronizedWithCurrentItem="True">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="130"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="22"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Grid.Column="0" BorderThickness="0" ItemsSource="{Binding Messages}" DisplayMemberPath="Raw" />
<ListBox Grid.Row="0" Grid.Column="1" BorderThickness="1,0,0,0" BorderBrush="#FFBBBBBB" ItemsSource="{Binding Users}" DisplayMemberPath="Nick" />
<TextBox Grid.Row="1" Grid.ColumnSpan="2" BorderThickness="0,1,0,0" BorderBrush="#FFBBBBBB" Height="22" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
The TabControl contains in each tab 2 list boxes and a textbox. One of the listboxes contains user names is is not necessary all the time.
There are 3 kinds of tabs, Server tabs, room tabs and private tabs. In private and server tabs the user list should not exist or be hidden.
I have an enum on the bound room object :
public enum IRCRoomType
{
Server,
Channel,
Private
}
How do I automatically hide the user list based on the enum, I have seen samples of 2 approaches, the binding on visibility with a converter or a trigger. Which is the better approach and are there any more?
When there are no tabs, and the first tab is created it is not automatically selected, how do I select it?
Is there a way of impacting the item styles inside the listboxes depending on tab type? How would I acheive this?
I am just looking for links/hints and not for actual solutions, but if you can give code then that would be a bonus!
It depends on how complicated code. If it's simple I rather use Trigger (you have everything which belows to UI in XAML), but if code is much more complicated consider using Converters (It's actually simpler to use it)
Bind to SelectedIndex of List and set it to 0?
Yes, of course, you can use ContentControl with DataTemplate (Or just DataTemplate in some cases) Some code where I use it:
<ListBox>
<ListBox.Resources>
<DataTemplate DataType="{x:Type your_namespace:your_type}">
... your code ...
</DataTemplate>
<DataTemplate DataType="{x:Type system:String}">
... your code ...
</DataTemplate>
</ListBox.Resources>
</ListBox>
Code you posted is actually a new Template, but you've changed the Style. Please consider override some Template.
Best regards

How to create dynamically listbox of stackpanels that contains UserControls in WPF?

How can one create in WPF listbox of stackpanels that contains some custom usercontrols of same type?
Listboxes and stackpanels should be scrollable. ListBox.Items are added dynamically and also UserControls to StackPanels.
I tried google, nothing like that found, only simple listbox examples with checkboxes and images.
Use ScrollViewer and ItemsControl
For example:
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Visible">
<ItemsControl x:Name="personsColl" ItemsSource="{Binding Path=Persons,
ElementName=CustomPersonInfoColl,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="personMainContainer">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- context for reapeat -->
<local:PersonInfo .... />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
do not forget
<UserControl x:Name="CustomPersonInfoColl" .... >
and back code
public partial class PersonInfoCollection : UserControl, INotifyPropertyChanged
{
[Bindable(true)]
public IList<ShortPersonInfo> Persons
{
get { return (IList<ShortPersonInfo>)this.GetValue(PersonsProperty); }
set { this.SetValue(PersonsProperty, value); OnPropertyChanged("Persons");}
}
public static readonly DependencyProperty PersonsProperty =
DependencyProperty.Register("Persons",
typeof(IList<ShortPersonInfo>),
typeof(PersonInfoCollection));
}
you can fill persons collection from other win forms or controls.

Templating ItemControl items without using ItemsSource

I am trying to build a custom Silverlight ItemsControl. I want the users of this control to add items using XAML. The items will be other UI elements. I would like to add a margin around all added items and therefore I want to add an ItemTemplate.
I am trying to do this using the ItemsControl.ItemTemplate, but that does not seem to be used when binding to elements in XAML, i.e using the ItemsControl.Items property.
However, if I use the ItemsControl.ItemsSource property, the ItemTemplate is used.
Is there anyway to use the ItemTemplate even though I am not assigning ItemsSource?
This is my code so far
<ItemsControl x:Class="MyControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate >
<toolkit:WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="20" Background="Red">
<TextBlock Text="Test text"/>
<ContentPresenter Content="{Binding}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate>
<Border>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ItemsPresenter x:Name="ItemsPresenter"/>
<Button Command="{Binding SearchCommand}"/>
</Grid>
</Border>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
And when I use my control
<MyControl>
<Button Content="Button"/>
<Button Content="Button"/>
</MyControl>
This got me a display of the items, with a wrap panel layout but no applied data template.
Then I found this post that mentioned two methods to override.
Son in my code-behind of the class I have now
protected override bool IsItemItsOwnContainerOverride(object item)
{
return false;
}
protected override void PrepareContainerForItemOverride(DependencyObject element,
object item)
{
base.PrepareContainerForItemOverride(element, item);
((ContentPresenter)element).ContentTemplate = ItemTemplate;
}
BUT - this gets me two items, with the style (I.E a red textblock) but no actual content. The buttons in the list are not added. It feels like I am doing something wrong - any pointers on what?
Thanks!
If all you want to do is add some margin then you can just set the ItemContainerStyle instead of specifying a template:
<ItemsControl>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="FrameworkElement.Margin" Value="10" /> <!-- or whatever margin you want -->
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
This will allow you to set any property of the container control (which in the case of an ItemsControl will be a ContentControl) through the style.

ItemsControl, VirtualizingStackPanel and ScrollViewer height

I want to display a important list of items using an ItemsControl.
The reason why I'm using an ItemsControl is that the DataTemplate is much more complex in the application I'm working on: The sample code provided only reflects the sizing problem I have.
I would like :
the ItemsControl to be virtualized because there is many items to display
its size to expand to its parent container automatically (the Grid)
<Grid>
<ItemsControl x:Name="My" ItemsSource="{Binding Path=Names}">
<ItemsControl.Template>
<ControlTemplate>
<StackPanel>
<StackPanel>
<TextBlock Text="this is a title" FontSize="15" />
<TextBlock Text="This is a description" />
</StackPanel>
<ScrollViewer CanContentScroll="True" Height="400px">
<VirtualizingStackPanel IsItemsHost="True" />
</ScrollViewer>
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
The code behind is :
public partial class Page1: Page
{
public List<string> Names { get; set; }
public Page1()
{
InitializeComponent();
Names = new List<string>();
for(int i = 0; i < 10000; i++)
Names.Add("Name : " + i);
My.DataContext = this;
}
}
As I force the ScrollViewer height to 400px, ItemsControl virtualization works as I expect: The ItemsControl displays the list very quickly, regardless of how many items it contains.
However, if I remove Height="400px", the list will expand its height to display the whole list, regardless its parent container height. Worse: it appears behind its container.
Putting a scrollviewer around the ItemsControl gives the expected visual result, but the virtualization goes away and the list takes too much time to display.
How can I achieve both automatic height expansion and virtualization of my ItemsControl ?
The problem is in the ItemsControl.Template: you use StackPanel there, which gives her children as much height as they want. Replace it to something like
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Text="this is a title" FontSize="15" />
<TextBlock Text="This is a description" />
</StackPanel>
<ScrollViewer CanContentScroll="True" Grid.Row="1">
<VirtualizingStackPanel />
</ScrollViewer>
</Grid>
And it should work fine.
Hope it helps.

Resources