Binding Itemscontrol in Listbox - c#

I want to binding my itemscontrol in listbox, but it doesn't work. I want to add some FrameworkElement to Listbox with stack style.
Here is my XAML code:
<ListBox x:Name="listThemes">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="5">
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Title}" FontWeight="Bold" />
<StackPanel Grid.Row="1" >
<ItemsControl Width="Auto"
Height="Auto"
ItemsSource="{Binding ElementName=listThemes, Path=Items}">
</ItemsControl>
</StackPanel>
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I don't know how to binding ItemsControl inside ListBox. I try put out the Binding ElementName of ItemsControl but it's always crashes. If the ElementName is page name, it not work.
Testing Class :
public class Testing
{
public string Title { get; set; }
public ObservableCollection<FrameworkElement> Items { get; set; }
}
C# Code :
observableCollection = new ObservableCollection<FrameworkElement>();
for (int i = 0; i < 3; i++)
{
observableCollection.Add(new Button { Content = i.ToString() });
observableCollection.Add(new Canvas
{
Background = new ImageBrush()
{
ImageSource = new BitmapImage(new Uri(#"Assets/ApplicationIcon.png", UriKind.Relative)),
Stretch = System.Windows.Media.Stretch.Fill
},
Height = 100,
Width = 100
});
}
List<Testing> list = new List<Testing>();
for (int i = 0; i < 3; i++)
{
Testing test = new Testing();
test.Title = "Testing";
test.Items = observableCollection;
list.Add(test);
}
listThemes.ItemsSource = list;

First, it crashes because you bind the same elements on three different item. One framework element can't be attach under two different control. So you should place your ObservableCollection in your List<Testing> creating process.
Second, you should set a DataContext to your listThemes control, so its items can find the right data path to bind and then you could remove the ElementName.
Try these code.
List<Testing> list = new List<Testing>();
for (int i = 0; i < 3; i++)
{
Testing test = new Testing();
test.Title = "Testing";
var observableCollection = new ObservableCollection<FrameworkElement>();
for (int j = 0; i < 3; i++)
{
observableCollection.Add(new Button { Content = j.ToString() });
observableCollection.Add(new Canvas
{
Background = new ImageBrush()
{
ImageSource = new BitmapImage(new Uri(#"Assets/ApplicationIcon.png", UriKind.Relative)),
Stretch = System.Windows.Media.Stretch.Fill
},
Height = 100,
Width = 100
});
}
test.Items = observableCollection;
list.Add(test);
}
listThemes.DataContext = list;
listThemes.ItemsSource = list;
Third, I don't think it is a good way to bind your FrameworkElement directly to an ItemsControl. It just look weird.
Update:
If you want to generate different types of items, you can use DataTemplateSelector. Because you already parse the forum data into different type, so please check this post:
How to Control the DataTemplateSelector in Windows Store Apps
It explains how to use DataTemplateSelector to display different type of data.

Why is your inner collection of type FrameworkElement?
Create a custom type for it and set an ItemTemplate for your inner ItemsControl.
<DataTemplate x:Key=NestedItemsTemplate>
....
</DataTemplate>
<ListBox x:Name="listThemes"
ItemsSource="{Binding TestItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="5">
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Title}" FontWeight="Bold" />
<StackPanel Grid.Row="1" >
<ItemsControl
Width="Auto" Height="Auto"
ItemsSource="{Binding ElementName=listThemes, Path=Items}"
ItemTemplate="{StaticResource NestedItemsTemplate}">
</ItemsControl>
</StackPanel>
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>

Related

Draw variable number of rectangles within WPF Grid cells?

I have a WPF Grid which is 3 columns wide and 8 rows:
<Window x:Class="Container.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="700" Width="1000">
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="10*" />
</Grid.ColumnDefinitions>
</Grid>
</Window>
I am using this to draw something like this:
Every cell in the first and third columns may have a different number of rectangles. Also, the width of each rectangle may be different and change at run-time. The width will be proportionate to a number (known at run-time and continually-changing).
What is the best way to draw these rectangles?
Here is what I've come up with after about an hour of fiddling (GitHub Repo):
I'm using the MVVM pattern to make the UI as easy as possible. Right now, it just populates with some random data.
The XAML:
<Window
x:Class="BuySellOrders.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BuySellOrders"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.DataContext>
<local:MainWindowVm />
</Window.DataContext>
<Grid Margin="15">
<ItemsControl ItemsSource="{Binding Path=Prices}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:PriceEntryVm}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Grid.Column="0"
Padding="5"
HorizontalAlignment="Stretch"
BorderBrush="Black"
BorderThickness="1">
<ItemsControl HorizontalAlignment="Right" ItemsSource="{Binding Path=BuyOrders}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:OrderVm}">
<Border
Width="{Binding Path=Qty}"
Margin="5"
Background="red"
BorderBrush="Black"
BorderThickness="1" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<Border
Grid.Column="1"
BorderBrush="Black"
BorderThickness="1">
<TextBlock
Margin="8"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Path=Price}" />
</Border>
<Border
Grid.Column="2"
Padding="5"
BorderBrush="Black"
BorderThickness="1">
<ItemsControl ItemsSource="{Binding Path=SellOrders}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:OrderVm}">
<Border
Width="{Binding Path=Qty}"
Margin="5"
Background="red"
BorderBrush="Black"
BorderThickness="1" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
The view models:
class MainWindowVm : ViewModel
{
public MainWindowVm()
{
var rnd = new Random();
Prices = new ObservableCollection<PriceEntryVm>();
for (int i = 0; i < 8; i++)
{
var entry = new PriceEntryVm();
Prices.Add(entry);
entry.BuyOrders.CollectionChanged += OnOrderChanged;
entry.SellOrders.CollectionChanged += OnOrderChanged;
entry.Price = (decimal)110.91 + (decimal)i / 100;
var numBuy = rnd.Next(5);
for (int orderIndex = 0; orderIndex < numBuy; orderIndex++)
{
var order = new OrderVm();
order.Qty = rnd.Next(70) + 5;
entry.BuyOrders.Add(order);
}
var numSell = rnd.Next(5);
for (int orderIOndex = 0; orderIOndex < numSell; orderIOndex++)
{
var order = new OrderVm();
order.Qty = rnd.Next(70) + 5;
entry.SellOrders.Add(order);
}
}
}
private void OnOrderChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var item in e.NewItems)
{
var order = item as OrderVm;
if (order.Qty > LargestOrder)
{
LargestOrder = order.Qty;
}
}
}
}
private int _largestOrder;
public int LargestOrder
{
get { return _largestOrder; }
private set { SetValue(ref _largestOrder, value); }
}
public ObservableCollection<PriceEntryVm> Prices { get; }
}
public class PriceEntryVm: ViewModel
{
public PriceEntryVm()
{
BuyOrders = new OrderList(this);
SellOrders = new OrderList(this);
}
private Decimal _price;
public Decimal Price
{
get {return _price;}
set {SetValue(ref _price, value);}
}
public OrderList BuyOrders { get; }
public OrderList SellOrders { get; }
}
public class OrderList : ObservableCollection<OrderVm>
{
public OrderList(PriceEntryVm priceEntry)
{
PriceEntry = priceEntry;
}
public PriceEntryVm PriceEntry { get; }
}
public class OrderVm : ViewModel
{
private int _qty;
public int Qty
{
get { return _qty; }
set { SetValue(ref _qty, value); }
}
}
I had to make some assumptions about the naming of things, but hopefully you should get the basic idea of what's going on.
It's structured as a list of PriceEntry, each of which contains a Price, and a BuyOrders and SellOrders properties.
BuyOrders and SellOrders are just lists of orders that have a Quantity property.
The XAML binds the list of price entries to a template that contains a 3 column grid. The first and 3rd columns of that grid bound to another set of item controls for each list of orders. The template for each order is just a border with a Width bound to the Quantity of the order.
All the binds means that just updating a property, or adding an order to either the buy or sell list of a price entry will automatically propagate to the UI. Adding or removing a PriceEntry will also automatically adjust the UI.
I haven't implemented your automatic scaling yet, but the basic idea would be to use a ValueConverter on the Quantity binding, to make it automatically adjust to the largest order.
As an extra note, it uses this nuget package to provide some of the MVVM boiler-plate code, but you should be able to use anything you want, as long as it gives you INotifyPropertyChanged support.
Here is a bonus screen capture showing the dynamic nature of MVVM updating the UI based on a timer.
This only needed a few lines of code to randomly pick a row, then randomly pick an order on the row, then add or subtract a small random amount from the quantity.
_updateTimer = new DispatcherTimer();
_updateTimer.Tick += OnUpdate;
_updateTimer.Interval = TimeSpan.FromSeconds(0.01);
_updateTimer.Start();
private void OnUpdate(object sender, EventArgs e)
{
var entryIndex = _rnd.Next(Prices.Count);
var entry = Prices[entryIndex];
OrderList list;
list = _rnd.Next(2) == 1 ?
entry.BuyOrders :
entry.SellOrders;
if (list.Any())
{
var order = list[_rnd.Next(list.Count)];
order.Qty += _rnd.Next(0, 8) - 4;
}
}
Right then, here goes....
This is exactly the kind of thing you want to use data-binding for. You can try and do things manually if you like, but your code will quickly become very messy if you do. WPF lets you do things the old-school way (i.e. similar to WinForms et al) but that was really to facilitate porting of legacy code. I won't go into too much detail about MVVM (plenty of info on the net about it), but you can get started by using NuGet to add MVVMLightLibs or some other MVVM framework to your project and then you assign your main window a view model by doing something like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
So now it's time for the view model itself, which is a model of the data structures that you want your view to display:
public class MainViewModel : ViewModelBase
{
public ObservableCollection<PriceLevel> PriceLevels { get; } = new ObservableCollection<PriceLevel>
{
new PriceLevel(110.98, new int[]{ }, new int[]{ }),
new PriceLevel(110.97, new int[]{ }, new int[]{ }),
new PriceLevel(110.96, new int[]{ }, new int[]{ }),
new PriceLevel(110.95, new int[]{ }, new int[]{ 5 }),
new PriceLevel(110.94, new int[]{ }, new int[]{ 3, 8 }),
new PriceLevel(110.93, new int[]{ 8, 3, 5, }, new int[]{ }),
new PriceLevel(110.92, new int[]{ 3 }, new int[]{ }),
new PriceLevel(110.91, new int[]{ }, new int[]{ }),
};
}
public class PriceLevel
{
public double Price { get; }
public ObservableCollection<int> BuyOrders { get; }
public ObservableCollection<int> SellOrders { get; }
public PriceLevel(double price, IEnumerable<int> buyOrders, IEnumerable<int> sellOrders)
{
this.Price = price;
this.BuyOrders = new ObservableCollection<int>(buyOrders);
this.SellOrders = new ObservableCollection<int>(sellOrders);
}
}
If you don't already know, ObservableCollection is very similar to list but it propegrates change notification, so when you make your view display the data in it your GUI will update automatically whenever the list changes. This MainViewModel class contains an ObservableCollection of type PriceLevel, and each PriceLevel contains the price and the lists of buy and sell orders. This means you'll be able to add and remove price points, and also add and remove the orders in the price points, and your front-end will reflect those changes.
So on to the front end itself:
<Window.Resources>
<!-- Style to display order list as horizontal list of red rectangles -->
<Style x:Key="OrderListStyle" TargetType="{x:Type ItemsControl}">
<!-- Set ItemsPanel to a horizontal StackPanel -->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<!-- Display each item in the order list as a red rectangle and scale x by 8*size -->
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1" Margin="5" >
<Rectangle Width="{Binding}" Height="20" Fill="Red">
<Rectangle.LayoutTransform>
<ScaleTransform ScaleX="8" ScaleY="1" />
</Rectangle.LayoutTransform>
</Rectangle>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Style to make Price cells vertically aligned -->
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- This style centers the column's header text -->
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</Window.Resources>
<!-- This datagrid displays the main list of PriceLevels -->
<DataGrid ItemsSource="{Binding PriceLevels}" AutoGenerateColumns="False" IsReadOnly="True"
CanUserAddRows="False" CanUserDeleteRows="False" CanUserReorderColumns="False" CanUserResizeColumns="False"
CanUserResizeRows="False" CanUserSortColumns="False" RowHeight="30">
<DataGrid.Columns>
<!-- The buy orders column -->
<DataGridTemplateColumn Header="Buy orders" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding BuyOrders}" Style="{StaticResource OrderListStyle}" HorizontalAlignment="Right" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- The price column -->
<DataGridTextColumn Header="Price" Width="Auto" Binding="{Binding Price}" />
<!-- The sell orders column -->
<DataGridTemplateColumn Header="Sell Orders" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding SellOrders}" Style="{StaticResource OrderListStyle}" HorizontalAlignment="Left" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Looks a bit full-on, but if you break it down into sections it's actually pretty straightforward. The main different between this and what you've been trying to do is that I'm using a DataGrid. This is basically a Grid control that's had extra functionality added to make it respond dynamically to data that it's been bound to. It also has a lot of extra stuff we dont' need (editing, column resize/reordering etc) so I've turned all that off. The DataGrid binds to PriceLevels in the view model, so it will display a vertical list showing each one. I've then explicitly declared the 3 columns you're after. The middle one is easy, it's just text, so DataGridTextColumn will, do the job. The other two are horizontal arrays of rectangles, so I've used DataGridTemplateColumn which allows me to customize exactly how they look. This customization is mostly done in the OrderListStyle at the very top of the XAML which sets ItemsPanel to a horizontal StackPanel and sets ItemTemplate to a rectangle. There's also a bit of XAML in there to scale the rectangle by a constant, according to the value of the integer it's displaying in the order list.
Here's the result:
I know the XAML might seem a little full-on, but keep in mind this is now fully data-bound to that view model and it will automatically update in response to changes. This little bit of extra work at the start results in MUCH cleaner update code which is also easier to test and debug.
Hope this is what you're after, if you have any questions let me know and we can take it into chat.
UPDATE: If you want to see the dynamic update in action then add this to your main view model's constructor, it just adds and removes orders randomly:
public MainViewModel()
{
var rng = new Random();
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(0.1);
timer.Tick += (s, e) =>
{
var row = this.PriceLevels[rng.Next(this.PriceLevels.Count())]; // get random row
switch (rng.Next(4))
{
case 0: row.BuyOrders.Add(1 + rng.Next(5)); break;
case 1: row.SellOrders.Add(1 + rng.Next(5)); break;
case 2: if (row.BuyOrders.Count() > 0) row.BuyOrders.RemoveAt(rng.Next(row.BuyOrders.Count())); break;
case 3: if (row.SellOrders.Count() > 0) row.SellOrders.RemoveAt(rng.Next(row.SellOrders.Count())); break;
}
};
timer.Start();
}

C# UWP create check-list table programmatically

I have task to create in C# UWP user created check-list.
But I have stuck from the beginning cause XAML is new for me, so I have no idea what to start from.
So, I have textbox to enter title, task or subtask to in listbox (priviously added to) selected task.
this is my xaml how it looks like now:
<Page
x:Class="Table1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Table1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<TextBox x:Name="txt" HorizontalAlignment="Left" Height="71" Margin="71,247,0,0" Text="TextBox" VerticalAlignment="Top" Width="395"/>
<RadioButton x:Name="title" Content="Add Title" HorizontalAlignment="Left" Margin="71,86,0,0" VerticalAlignment="Top"/>
<RadioButton x:Name="task" Content="Add Task" HorizontalAlignment="Left" Margin="71,123,0,0" VerticalAlignment="Top"/>
<RadioButton x:Name="subtask" Content="Add Subtask" HorizontalAlignment="Left" Margin="71,155,0,0" VerticalAlignment="Top"/>
<ListBox x:Name="listbox" HorizontalAlignment="Left" Height="68" Margin="71,354,0,0" VerticalAlignment="Top" Width="395"/>
<Button x:Name="btn" Content="Button" HorizontalAlignment="Left" Margin="401,483,0,0" VerticalAlignment="Top" Click="btn_Click"/>
</Grid>
</Page>
There are the code:
public class subtasks
{
public string parent { get; set; }
public string subtask { get; set; }
public subtasks(string parenti, string subtaski)
{
parent = parenti;
subtask = subtaski;
}
public void setsub(string parenti, string sub)
{
parent = parenti;
subtask = sub;
}
}
List<string> Tasks = new List<string>();
List<subtasks> sub = new List<subtasks>();
private void btn_Click(object sender, RoutedEventArgs e)
{
string parent = "";
string Title;
string Task;
string Subtask;
if (title.IsChecked==true)
{
Title = txt.Text;
adding(Title, parent, 1);
}
else if (task.IsChecked==true)
{
Task = txt.Text;
adding(Task, parent, 2);
}
else if (subtask.IsChecked==true)
{
parent = listbox.SelectedItem.ToString();
Subtask = txt.Text;
adding(Subtask, parent, 3);
}
else
{
}
}
private void adding(string str, string par, int x)
{
subtasks subi = new subtasks(par,str);
RowDefinition row = new RowDefinition();
TextBlock text = new TextBlock();
if (x==1)
{
print(str);
}
else if (x==2)
{
Tasks.Add(str);
listbox.Items.Add(str);
text.Text = str;
print(str);
}
else
{
sub.Add(subi);
print(str);
}
}
private void print(string title)
{
int step = 0;
Grid gridwin = new Grid();
gridwin.Children.Clear();
RowDefinition row = new RowDefinition();
TextBlock text = new TextBlock();
text.Text = title;
Grid.SetColumn(text, 0);
Grid.SetRow(text, step);
step++;
for (int i = 0; i < Tasks.Count; i++)
{
text.Text = Tasks[i].ToString();
gridwin.Children.Add(text);
Grid.SetColumn(text, 0);
Grid.SetRow(text, step);
step++;
for (int k = 0; k < sub.Count; k++)
{
if (sub[k].parent == Tasks[i])
{
text.Text = sub[k].subtask.ToString();
gridwin.Children.Add(text);
Grid.SetColumn(text, 0);
Grid.SetRow(text, step);
step++;
}
}
}
}
As you see I need to clear and put data every time the button is clicked, cause you never know when user will decide to add new subtask for previously added task. So, the question is, how to make the table with column1 with tasks and subtasks and column2 which is chekbox.
What you want to probably do is to create a DataTemplate. You use this to specify how list items should be displayed and formatted. This way you can specify you want to lay them out as a Grid with two columns like description and CheckBox. Take a look into the documentation to see some examples of DataTemplates. You can also see the Azure Mobile Apps quickstart for UWP, because although it is focused on demonstrating Microsoft Azure integration to UWP, it is actually a to-do app, which should give you some inspiration for building your own.
The layout could look like this:
<ListBox x:Name="listbox" HorizontalAlignment="Left" Height="68" Margin="71,354,0,0" VerticalAlignment="Top" Width="395">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Text}" />
<CheckBox Grid.Column="1" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You can see my code is also using {Binding} syntax, which you will also need to learn a bit about to be able to know when the user has checked a to-do item in the list. I suggest you to take a look at a simple tutorial sample like here. In fact, data-binding is one of the most important things when building XAML-based apps and when you get to understand this concept, it will help you a lot on the way to becoming a UWP ninja :-) .
Why dont use the UWP DataGrid with CheckBox?
XAML
<toolkit:DataGrid Grid.Column="0" ItemsSource="{x:Bind myItemsToBind}"
x:Name="dgwDeviceSPNs" MinWidth="100"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
AlternatingRowBackground="Transparent"
AreRowDetailsFrozen="False"
AreRowGroupHeadersFrozen="True"
AutoGenerateColumns="False"
CanUserSortColumns="False"
CanUserReorderColumns="True"
RowGroupHeaderPropertyNameAlternative=""
CanUserResizeColumns="True"
MaxColumnWidth="200"
FrozenColumnCount="0"
GridLinesVisibility="Horizontal"
HeadersVisibility="None"
IsReadOnly="True"
RowDetailsVisibilityMode="Collapsed"
SelectionMode="Single">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTemplateColumn MinWidth="10">
<toolkit:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Padding="2">
<CheckBox ToolTipService.ToolTip="{Binding Name}" IsChecked="{Binding IsSelected, Mode=TwoWay}" Content="{Binding Name}"></CheckBox>
</StackPanel>
</DataTemplate>
</toolkit:DataGridTemplateColumn.CellTemplate>
</toolkit:DataGridTemplateColumn>
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>

Dynamically sizing 2 dimensional Grid

I am working on creating the card game "Memory" In WPF. I am having trouble on the UI side of it. I have set it up so that when the user selects a difficulty it dynamically sets the size of the deck (4x4 for easy, this is what we will be working/talking about for proof of concept). How do I allow for the dynamic change of grid when selecting different difficulties?
This is where you set the difficulty (All the cards are for testing purposes..)
private void SetDifficulty(Difficulty difficulty) {
//Clearing CardList
CardList.Clear();
//Switching on the diff
switch (difficulty) {
case Difficulty.Easy:
CardList = new ObservableCollection<Card>{
new Card {
Image = Resources.Bowser
},
new Card(),
new Card(),
new Card(),
new Card(),
new Card(),
new Card(),
new Card(),
new Card(),
new Card(),
new Card(),
new Card(),
new Card(),
new Card(),
new Card(),
new Card()
};
break;
case Difficulty.Medium:
break;
case Difficulty.Hard:
break;
default:
throw new ArgumentOutOfRangeException(nameof(difficulty), difficulty, null);
}
}
XAML:
<Window x:Class="MemoryGame.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MemoryGame.Views"
xmlns:viewModels="clr-namespace:MemoryGame.ViewModels"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance viewModels:MemoryGameViewModel}"
Title="MainView" Height="300" Width="300">
<Grid ShowGridLines="True">
<ListBox ItemsSource="{Binding Path=CardList}">
<ListBox.ItemTemplate>
<DataTemplate DataType="viewModels:Card">
<StackPanel Orientation="Horizontal" Width="50" Height="50" >
<!--<Image Source="/Pictures/Luigi.jpg"></Image>-->
<Button Content="{Binding Image, UpdateSourceTrigger=PropertyChanged}" Margin="5" Height="50" Width="50">
</Button>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I would prefer using ItemsControl over ListBox for the collection in the XAML. I would also BIND the width of the collection and add wrapper panel that allows for wrapping objects:
<ItemsControl ItemsSource="{Binding Path=CardList} Width="{Binding CollWidth}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="viewModels:Card">
<StackPanel Orientation="Horizontal" Width="50" Height="50" >
<!--<Image Source="/Pictures/Luigi.jpg"></Image>-->
<Button Content="{Binding Image, UpdateSourceTrigger=PropertyChanged}" Margin="5" Height="50" Width="50">
</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- A WrapPanel ensures the items wrap to the next line -->
<!-- when it runs out of room in the collection dimensions -->
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Don't forget to add CollWidth property:
private int _collWidth;
public int CollWidth {
get { return _collWidth; }
set {
_collWidth = value;
OnPropertyChanged("CollWidth");
}
}
Now you can easily modify your SetDificulty method:
private void SetDifficulty(Difficulty difficulty) {
// this auto clears everything
CardList = new ObservableCollection<Card>();
//Switching on the diff
switch (difficulty) {
case Difficulty.Easy:
// set the width in each level of difficulty to allow wrapper to make nice looking grid
CollWidth = 200; // (button width is 50) * 4 buttons = 200
for(int i=0;i<16;i++)
CardList.Add(new Card()); // or whatever constructor
break;
case Difficulty.Medium:
CollWidth = 250; // (button width is 50) * 5 buttons = 250
for(int i=0;i<25;i++)
CardList.Add(new Card()); // or whatever constructor
break;
case Difficulty.Hard:
CollWidth = 300; // (button width is 50) * 6 buttons = 300
for(int i=0;i<36;i++)
CardList.Add(new Card()); // or whatever constructor
break;
default:
throw new ArgumentOutOfRangeException(nameof(difficulty), difficulty, null);
}
}
You do not have to worry about the Height of the collection. The added <WrapPanel> will take care of that for you. You only specify the CollWidth and the wrap panel will put CollWidth/50 buttons in each row
You could use a UniformGrid as ItemsPanel of your ListBox, and bind its Columns or Rows property to a property in your view model:
<ListBox ItemsSource="{Binding CardList}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding GridSize}"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Image}" Margin="5" Height="50" Width="50"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As a note, setting UpdateSourceTrigger=PropertyChanged on the Content Binding of the Button makes no sense. It only has an effect on Bindings that actually trigger an update of their source property, i.e. TwoWay or OneWayToSource Bindings.
It is also bad practice to create UI elements in code behind, like the Image property of your Card class. Better declare a property of type ImageSource, and bind an Image control's Source property in XAML:
public class Card
{
public ImageSource Image { get; set; }
...
}
...
var card = new Card
{
Image = new BitmapeImage(new Uri("pack://application:,,,/Picures/Luigi.jpg"))
};
XAML:
<ListBox.ItemTemplate>
<DataTemplate>
<Button Margin="5" Height="50" Width="50">
<Image Source="{Binding Image}"/>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>

UWP update a itemscontrol with an array that keeps updating

I have an array that keeps changing its values, because of this I want to have the apps UI refreshing every time the array's values do. I have this bound with an itemsControl. I can show the first array's values but then I can't update them I have tried .items.Clear() but its not working. Here are snippets of the .xaml and the xaml.cs. I actually took the code of the .xaml from a question from this site.
.xaml
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox Text="Testing" IsReadOnly="True"></TextBox>
<ItemsControl x:Name="itemsControl"
ItemsSource="{Binding itemsControl}"
FontSize="24">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="Auto"
Margin="0 12"
HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Column="0"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk0" Text="{Binding}" />
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
.xaml.cs
String c = (new String(cArray));
string[] arr = null;
string[] data = null;
if (c != null)
{
arr = c.Split('\n');
if (arr.Length > 0)
{
data = arr[0].Split(',');
}
}
for(int index = 0; index < 4; index++)
{
itemsControl.Items.Add(float.Parse(data[index]));
}
itemsControl.Clear();
If anyone has an idea of how I can do this I will be very grateful, thanks in advance and I will try to answer any questions as soon as possible!
What you're missing is an understanding of how bindings are triggered to update.
The INotifyPropertyChanged interface contains a method (PropertyChanged) and when called and passed the name of a property will tell the binding system that the property has changed and the binding should be updated.
INotifyCollectionChanged is the equivalent for collections, and communicates when a collection has changed. i.e. something added, removed, or the list cleared.
ObservableCollection<T> contains an implementation of INotifyCollectionChanged that makes it easy to work with lists, collections, etc. that change.
If you used an ObservableCollection<float> instead of an array you'd be able to modify the list and have the UI updated to reflect this easily.
As a starter, see the following which demonstrates how easy it is to use an ObservableCollection.
XAML:
<StackPanel>
<Button Click="Button_Click">add an item</Button>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
code behind;
public MainPage()
{
this.InitializeComponent();
// Initialize the property
this.Items = new ObservableCollection<string>();
// Use self as datacontext (but would normally use a separate viewmodel)
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// add a new item to the UI
this.Items.Add(DateTime.Now.ToString());
}
// The "collection" that is shown in the UI
public ObservableCollection<string> Items { get; set; }

How to display array in xaml under UWP C#

How to display array in xaml the way that each line is on separate row and each row is split into separate textblocks?
I split line like so:
string[] splitLines = line.Split(';');
itemsControl.Items.Add(line);
Then, in xaml I display it like so:
<ItemsControl x:Name="itemsControl"
ItemsSource="{Binding itemsControl}"
FontSize="24">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="Auto"
Margin="0 12"
HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Column="0"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk0" Text="{Binding }" />
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This way I get each line from my .xls file in separate row, but the separator ';' is included in the returned content of row.
Now I would like to remove ';' separator and put each word from each line on separate textblock, like so:
for (int index = 0; index < splitLines.Length; index++)
{
itemsControl.Items.Add(splitLines[index]);
}
This breaks each line into separate words, but unfortunately, each word is put on separate row(instead to populate 5 textblocks with each line's words.
Any idea how to show each .xls line in separate row, but then also split each line into words and put them in separate textblocks in separate columns?
EDIT
I have tried the solution from Alexej Sommer, I get some erors though:
ItemsData dataitem = new ItemsData
{
value0 = splitLines[0];
value1 = splitLines[1];
value2 = splitLines[2];
value3 = splitLines[3];
value4 = splitLines[4];
}
items.Add(dataitem);
In this piece of code I get first semicolon underscored and error says:
} expected.
Then, values 1 - 4 are underscored and error says:
The name (X) does not exist in the current content
At last, dataitem gets underscored and same error shows up.
EDIT 2 --
I managed to fix the problem. There were problems with braces and code put in a wrong place. Also, instead of:
ItemsData dataitem = new ItemsData
{
value0 = splitLines[0];
value1 = splitLines[1];
value2 = splitLines[2];
value3 = splitLines[3];
value4 = splitLines[4];
}
items.Add(dataitem);
I used:
ItemsData dataitem = new ItemsData
{
value0 = splitLines[0],
value1 = splitLines[1],
value2 = splitLines[2],
value3 = splitLines[3],
value4 = splitLines[4],
};
items.Add(dataitem);
And that solved the problem.
Here is clean working code:
public async void ReadFile()
{
var path = #"CPU.xls";
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
var file = await folder.GetFileAsync(path);
var readFile = await Windows.Storage.FileIO.ReadLinesAsync(file);
foreach (string line in readFile.OrderBy(line =>
{
int lineNo;
var success = int.TryParse(line.Split(';')[4], out lineNo);
if (success) return lineNo;
return int.MaxValue;
}))
{
string[] splitLines = line.Split(';');
ObservableCollection<ItemsData> items = new ObservableCollection<ItemsData>();
for (int index = 0; index < splitLines.Length; index++)
{
ItemsData dataitem = new ItemsData
{
value0 = splitLines[0],
value1 = splitLines[1],
value2 = splitLines[2],
value3 = splitLines[3],
value4 = splitLines[4],
};
items.Add(dataitem);
}
itemsControl.DataContext = items;
}
}
I yet need to fix the layout as for now all the textblocks show up in same column and they overlap.
Thank you Alexej for your help!
Edit 3----
To put textblocks on separate columns in xaml I did like so:
<ScrollViewer>
<ItemsControl x:Name="itemsControl"
ItemsSource="{Binding}"
FontSize="24">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="Auto"
Margin="0 12"
HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Column="0"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk0" Text="{Binding value0}" />
</StackPanel>
<StackPanel Grid.Column="1"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk1" Text="{Binding value1}" />
</StackPanel>
<StackPanel Grid.Column="2"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk2" Text="{Binding value2}" />
</StackPanel>
<StackPanel Grid.Column="3"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk3" Text="{Binding value3}" />
</StackPanel>
<StackPanel Grid.Column="4"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk4" Text="{Binding value4}" />
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
This way each word from line is put on separate column. TextBlocks don't overlap anymore.
Unfortunately I didn't manage do showcase all file(all lines). For now each row in xaml is populated with same line from the file instead of pulling each line into separate rows in xaml.
What should I do in order to list all lines instead of just one line?
I believe that there is problem on C# code side.
First create class for data:
public class itemsdata
{
public string txt0 { get; set; }
public string txt1 { get; set; }
public string txt2 { get; set; }
public string txt3 { get; set; }
public string txt4 { get; set; }
}
after it, create collection
ObservableCollection<itemsdata> items = new ObservableCollection<itemsdata>();
and fill it:
for (int index = 0; index < splitLines.Length; index++)
{
itemsdata dataitem=new itemsdata
{
txt0=splitLines[0];
txt1=splitLines[1];
txt2=splitLines[2];
txt2=splitLines[3];
txt2=splitLines[4];
}
items.Add(dataitem);
}
itemsControl.DataContext = items;
change binging to
ItemsSource="{Binding}"
and add new fields to datatemplate
<TextBlock Name="txtblk0" Text="{Binding txt0}" />
<TextBlock Name="txtblk1" Text="{Binding txt1}" />
<TextBlock Name="txtblk2" Text="{Binding txt2}" />
<TextBlock Name="txtblk3" Text="{Binding txt3}" />
<TextBlock Name="txtblk4" Text="{Binding txt4}" />

Categories

Resources