WPF: Using the ItemsControl DataTemplate in ComboBox - c#

I'm learning WPF and I'm trying to do something simple. I have two classes: Candy and MyColor. The code of these two classes look like this
public class Candy
{
public MyColor Color { get; set; }
public string Name { get; set; }
}
public class MyColor
{
public string Name { get; set; }
public uint Id { get; set; }
}
(I have attached an image below to make it clearer)
I have an area in the window in which I can create a MyColor by using a textbox that inserts the MyColor.Name, and a simple logic which increments the MyColor.Id. On the other side of the window, I have a button that creates new item in a ItemsControl which holds Candy. Within this ItemsControl there is an ComboBox which I can specify Candy.Color and a TextBox which I specify the Candy.Name. Finally, when I hit the button Generate List the code should output in the TextBox below a list in the format of
Candy.Color Candy.Name
I'm trying to figure out how to automatically populate the ComboBox filled with a list of Colors I have created so I can specify the Candy color, but I don't know how to bind my data source. Also, how would I generate the text?
Currently my code looks like this
namespace QuestionToAsk
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ObservableCollection<MyColor> Colors;
ObservableCollection<Candy> Candies;
public MainWindow()
{
InitializeComponent();
Colors = new ObservableCollection<MyColor>();
Candies = new ObservableCollection<Candy>();
Colors.Add(new MyColor() { Name = "(Unspecified)", Id = 0 });
icColors.ItemsSource = Colors;
icCandies.ItemsSource = Candies;
}
private void btnColor(object sender, RoutedEventArgs e)
{
if (txtColor.Text != "")
{
uint last_id = Colors.Last<MyColor>().Id;
Colors.Add(new MyColor() { Name = txtColor.Text, Id = last_id+1 });
txtColor.Text = "";
}
}
private void btnNewCandy(object sender, RoutedEventArgs e)
{
Candies.Add(new Candy());
}
private void btnGetList(object sender, RoutedEventArgs e)
{
//How to create the list of <Color, Name>?
}
}
public class Candy
{
public MyColor Color { get; set; }
public string Name { get; set; }
}
public class MyColor
{
public string Name { get; set; }
public uint Id { get; set; }
}
}
And my XML file looks like this:
<Window x:Class="QuestionToAsk.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:QuestionToAsk"
Title="Color Candy Maker" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DockPanel Margin="3">
<Button Content="Add Color" Click="btnColor" DockPanel.Dock="Bottom"/>
<TextBox x:Name="txtColor" DockPanel.Dock="Bottom"/>
<ItemsControl x:Name="icColors" Grid.Column="0" Grid.Row="0" DockPanel.Dock="Top">
<ItemsControl.ItemTemplate>
<DataTemplate x:Name="tColorsTemplate">
<TextBlock Text="{Binding Name}" Name="Color" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
<DockPanel Grid.Column="1" Grid.Row="0" Margin="3">
<Grid DockPanel.Dock="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Content="New Candy" Click="btnNewCandy" Grid.Column="0"/>
<Button Content="Generate List" Click="btnGetList" Grid.Column="1"/>
</Grid>
<ItemsControl Name="icCandies" DockPanel.Dock="Top">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ComboBox Name="cmbColors" Grid.Column="0">
<!-- How to bind this cmbColors to icColors? -->
</ComboBox>
<TextBox Text="{Binding Name}" Grid.Column="1" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
<DockPanel Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<TextBox x:Name="txtColorCandy"/>
</DockPanel>
</Grid>
</Window>

EDIT
I have removed all my ideas and implemented it using your hard design :D.
The xaml code
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DockPanel Margin="3">
<Button Content="Add Color" Click="btnColor" DockPanel.Dock="Bottom"/>
<TextBox x:Name="txtColor" DockPanel.Dock="Bottom"/>
<ItemsControl x:Name="icColors" Grid.Column="0" Grid.Row="0" DockPanel.Dock="Top">
<ItemsControl.ItemTemplate>
<DataTemplate x:Name="tColorsTemplate">
<TextBlock Text="{Binding Name}" Name="Color" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
<DockPanel Grid.Column="1" Grid.Row="0" Margin="3">
<Grid DockPanel.Dock="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Content="New Candy" Click="btnNewCandy" Grid.Column="0"/>
<Button Content="Generate List" Click="btnGetList" Grid.Column="1"/>
</Grid>
<ItemsControl Name="icCandies" DockPanel.Dock="Top">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ComboBox Name="cmbColors" Grid.Column="0"
ItemsSource="{Binding Colors, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
DisplayMemberPath="Name"
SelectedItem="{Binding Color}">
</ComboBox>
<TextBox Text="{Binding Name}" Grid.Column="1" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
<DockPanel Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<TextBox x:Name="txtColorCandy" VerticalScrollBarVisibility="Auto"/>
</DockPanel>
</Grid>
Note the usage of RelativeSource since the values of ComboBox was in a property of the Window class while the selected value was to be saved in the Candy class. So the ItemsSource is bound to a property of Window class and the SelectedItem is bound to a property of the Candy class
Code Behind
public partial class MainWindow : Window
{
private ObservableCollection<MyColor> _colors;
public IEnumerable<MyColor> Colors
{
get { return _colors; }
}
private ObservableCollection<Candy> _candies;
public IEnumerable<Candy> Candies
{
get { return _candies; }
}
public MainWindow()
{
InitializeComponent();
_colors = new ObservableCollection<MyColor>();
_candies = new ObservableCollection<Candy>();
_colors.Add(new MyColor { Name = "(Unspecified)", Id = 0 });
icColors.ItemsSource = Colors;
icCandies.ItemsSource = Candies;
}
private void btnColor(object sender, RoutedEventArgs e)
{
if (txtColor.Text != "")
{
uint last_id = Colors.Last<MyColor>().Id;
_colors.Add(new MyColor() { Name = txtColor.Text, Id = last_id + 1 });
txtColor.Text = "";
}
}
private void btnNewCandy(object sender, RoutedEventArgs e)
{
_candies.Add(new Candy());
}
private void btnGetList(object sender, RoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
foreach (var item in _candies)
{
if (item.Name == null || item.Color == null)
continue;
sb.AppendLine(item.Color.Name + " " + item.Name);
}
txtColorCandy.Text = sb.ToString();
}
}
It there are any confusion let me know and I will try to help

Related

UWP independent scrollviews

The idea is to have a one column with lots of elements in a scrollable listview and a second column (not scrollable) with small details. The problem is that entire page is scrolling and as a result the second column is out of the view.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Scrollviewer x:Name="scrollviewer" Grid.Column="0">
<Listview ItemsSource={...} ItemTemplate={...} />
</Scrollviewer>
<StackPanel Grid.Column="1">
<...>
</StackPanel>
</Grid>
I've tried to link window height to scrollviewer height with SizeChanged event (scrollviewer.Height = Window.Current.Bounds.Height), and it works kind of until I scroll for a little bit and then it crashes with an error "Layout cycle detected".
I think it supposed to be very common scenario and I am missing something here. Can anyone help?
I can’t reproduce your issue.
You could check the following sample to compare your code.
MainPage.xaml:
<Page
..>
<Grid>
<NavigationView PaneDisplayMode="Left" ItemInvoked="NavigationView_ItemInvoked">
<NavigationView.MenuItems >
<NavigationViewItem Content="A" x:Name="A" />
<NavigationViewItem Content="B" x:Name="B" />
<NavigationViewItem Content="C" x:Name="C" />
</NavigationView.MenuItems>
<Frame x:Name="ContentFrame"/>
</NavigationView>
</Grid>
MainPage.xaml.cs:
private void NavigationView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
{
var item = args.InvokedItemContainer;
switch (item.Name)
{
case "B":
ContentFrame.Navigate(typeof(BlankPage1));
break;
}
}
BlankPage1.xaml:
<Page
..>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListView x:Name="listView" ItemsSource="{x:Bind Fruits}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Fruit">
<TextBlock Text="{x:Bind price}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Grid.Column="1">
<TextBlock Text="yyyyyyyyygggg"/>
<TextBlock Text="hkcsduhgfuhiualhfijht" />
</StackPanel>
</Grid>
BlankPage1.xaml.cs:
public sealed partial class BlankPage1 : Page
{
public ObservableCollection<Fruit> Fruits { get; set; }
public BlankPage1()
{
this.InitializeComponent();
Fruits = new ObservableCollection<Fruit>()
{
new Fruit(){name="apple",price=12},
new Fruit(){name="peach",price=15},
new Fruit(){name="pear",price=8},
new Fruit(){name="banana",price=31},
new Fruit(){name="grape",price=5},
.......
// Add items that fill the entire page
new Fruit(){name="banana",price=31},
new Fruit(){name="grape",price=5},
new Fruit(){name="apple",price=12}
};
}
}
public class Fruit
{
public string name { get; set; }
public int price { get; set; }
}

How can I use 2 data bindings for an ItemSource Data Template in WPF App

I have a counter which increments depending on a foreach Loop from:
public partial class UserControlCounter : UserControl, INotifyPropertyChanged
{
private int _scanStatusCounter;
public int ScanStatusCounter
{
get { return _scanStatusCounter; }
set { _scanStatusCounter = value; NotifyPropertyChanged(); }
}
public UserControlCounter()
{
InitializeComponent();
DataContext = this;
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
await Task.Run(getAll);
scanStatus.Text = "Persons " + ScanStatusCounter.ToString();
}
private async void getAll()
{
//grab data and iterate it
string[] string_array = new string[] { "a", "b", "c", "d", "e" }; // type = System.String[]
foreach (var i in string_array)
{
ScanStatusCounter++;
await Task.Delay(100);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
and the Xaml:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Grid.Column="0" Name="myListBox" Height="40" ></ListBox>
<TextBlock Grid.Row="0" Grid.Column="1" Name="scanStatus" Height="40" Text="{Binding Path=ScanStatusCounter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBlock>
<Button Grid.Row="0" Grid.Column="2" Click="Button_Click" Height="40">Click Me</Button>
</Grid>
That works fine and increments "live", but my problem is that I need to get that now running inside a more complex Data Template.
I've tried to inject the Counter onClick with:
private async void Button_Click(object sender, RoutedEventArgs e)
{
var btn = sender as Button;
var contentPresenter = (btn.TemplatedParent as ContentPresenter);
var ppStatusCounter = contentPresenter.ContentTemplate.FindName("scanStatus", contentPresenter) as TextBlock;
ppStatusCounter.Text = "Entrys found: " + ScanStatusCounter.ToString();
}
But then I get the Value only onClick not like a live incrementing Counter, that's my Data Template:
<UserControl.Resources>
<c1:NameList x:Key="NameListData"/>
<DataTemplate x:Key="NameItemTemplate">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Domaine"/>
<TextBox Grid.Row="1" Grid.Column="0" x:Name="txtDomainName" Text="{Binding Path=DomaineName}" Margin="0,0,5,0"></TextBox>
<Button Grid.Row="1" Grid.Column="1" Width="100" Height="30" Margin="5,5,5,5" x:Name="btnNames" Click="Button_Click" Content="Start Scan" />
<Grid Grid.Column="2" Grid.Row="1" Height="20">
<ProgressBar x:Name="pbStatus" Height="20" Width="100" Minimum="0" Maximum="100" Visibility="Hidden"/>
<TextBlock x:Name="pbStatusText" HorizontalAlignment="Center" VerticalAlignment="Center" Text="Scanning" Visibility="Hidden"/>
</Grid>
<TextBlock Grid.Column="3" Grid.Row="1" Name="scanStatus" Text="{Binding Path=ScanStatusCounter, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Horizontal" VerticalAlignment="top">
<StackPanel Orientation="Horizontal" Margin="0,20,0,0">
<ListBox x:Name="lstDomainNames"
Margin="5,5,5,5"
ItemsSource="{Binding Source={StaticResource NameListData}}"
ItemTemplate="{StaticResource NameItemTemplate}"
IsSynchronizedWithCurrentItem="True"/>
</StackPanel>
</StackPanel>
</Grid>
that's my Data Source Class, not much there:
public partial class NameList : ObservableCollection<SetCredentials>
{
private static Logger logger = LogManager.GetCurrentClassLogger();
public NameList() : base()
{
using var forest = Forest.GetCurrentForest();
Forest currentForest = Forest.GetCurrentForest();
DomainCollection domains = currentForest.Domains;
foreach (Domain objDomain in domains)
{
Add(new SetCredentials(objDomain.ToString()));
}
}
}
public class SetCredentials
{
private string domainName;
public SetCredentials(string domain)
{
this.domainName = domain;
}
public string DomaineName
{
get { return domainName; }
set { domainName = value; }
}
}
Do I have to add a second Data Binding source to my ItemSource or do I need another approach for this, for example in my TextBox Binding?

Setting button property to invisible in a lisbox based on listitems

I am new to C# and WPF and still learning the ropes. I am currently trying use a ListBox to display some predefined items in a list. I am using an ObservableCollection to hold those items and I am binding that collection to that ListBox. I am also allowing the user to add new items to the list or update selected ones in addition to deleting them. For each item in that list I want to display a DELETE button beside it. However each button should only be visible for the items that have been added by the user and not any of the predefined items.
I am currently able to display the DELETE button for each item in the list. Therefore my question is, is it possible to set the the property of the DELETE button for each item in the list to be visible only for the items that were newly added to it and have no DELETE buttons showing for the predefined(default) items? If so, how would I go about doing that? (That is what I am struggling to figure out.)
Should I post my code?
Thanks
Here is the viewmode which has the list and the controls to add new items to the list.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox x:Name="DrinksListBox" HorizontalAlignment="Center" Height="325" Width="275" Margin="0,0,0,0" VerticalAlignment="Center" Grid.Column="0">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Type}" Width="80" Margin="0,0,10,0" Grid.Column="0"/>
<TextBlock Text="{Binding Name}" Width="80" Margin="0,0,10,0" Grid.Column="1" HorizontalAlignment="Left"/>
<Button x:Name="DrinkDeleteButton" Content="Delete" Click="CmdDeleteDrink_Clicked" HorizontalAlignment="Right" Grid.Column="2"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="DrinkNameTextBox" Grid.Column="1" HorizontalAlignment="Left" Height="45" Margin="0,0,0,100" TextWrapping="Wrap" Text="Enter Drink Name" VerticalAlignment="Center" Width="240" FontSize="20" VerticalContentAlignment="Center"/>
<ComboBox x:Name="DrinkTypeComboBox" Grid.Column="1" HorizontalAlignment="Left" Margin="0,47,0,0" VerticalAlignment="Top" Width="240" Height="45" ItemsSource="{Binding Drinks, Mode=OneWay}" DisplayMemberPath="Type" FontSize="20"/>
<Button x:Name="AddDrinkButton" Content="Add Drink" Grid.Column="1" HorizontalAlignment="Right" Margin="0,0,10,100" VerticalAlignment="Center" Width="100" Height="45" Click="CmdAddDrink_Clicked"/>
</Grid>
Here is my code-behind. I have a inner class for the drink property and the main class that sets up the list to be used.
public partial class MainWindow : Window
{
public ObservableCollection<Drinks> Drinks { get; private set; }
public MainWindow()
{
InitializeComponent();
Drinks = new ObservableCollection<Drinks>();
Drinks.Add(new Drinks("Soda", "Pepsi"));
Drinks.Add(new Drinks("Tea", "Lemon"));
Drinks.Add(new Drinks("Caffinated", "Coffee"));
Drinks.Add(new Drinks("Other", "Water"));
DrinksListBox.ItemsSource = Drinks;
DrinkTypeComboBox.ItemsSource = Drinks;
}
private void CmdDeleteDrink_Clicked(object sender, RoutedEventArgs e)
{
Button cmd = (Button)sender;
if (cmd.DataContext is Drinks deleteDrink)
{
Drinks.Remove(deleteDrink);
}
}
private void CmdAddDrink_Clicked(object sender, RoutedEventArgs e)
{
string typeSelection = ((Drinks)DrinkTypeComboBox.SelectedItem).Type;
Drinks.Add(new Drinks(typeSelection, DrinkNameTextBox.Text));
}
}
Drink class has the type of drink and a name for it.
public class Drinks
{
private string type;
private string name;
public Drinks(string type, string name)
{
this.type = type;
this.name = name;
}
public string Type
{
get { return type; }
set
{
if (type != value)
{
type = value;
}
}
}
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
}
}
}
}
Let's say you have your item:
public class Drinks
{
//your properties, simplified for clarity
public string Name {get;set;}
public string Type {get;set;}
//hey, a new one!
public bool IsUserDefined {get;set;}
}
Then, when the user adds one:
private void CmdAddDrink_Clicked(object sender, RoutedEventArgs e)
{
string typeSelection = ((Drinks)DrinkTypeComboBox.SelectedItem).Type;
Drinks.Add(new Drinks(typeSelection, DrinkNameTextBox.Text)
{
IsUserDefined = true
});
}
disclaimer: from the top of my head; normally that means syntax errors; removed some parts for clarity.
<!-- In your resources section of the XAML -->
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<ListBox x:Name="DrinksListBox" ItemSource="{Binding Drinks}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Type}"/>
<TextBlock Text="{Binding Name}"/>
<Button x:Name="DrinkDeleteButton"
Visibility="{Binding Path=IsUserDefined,
Converter={StaticResource BoolToVis}}"/>
<!-- note: left out some attributes for clarity -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
That should do the trick.
Btw, you seem to be mixing some typical MVVM style coding and code-behind coding. It's worth to say that you might benefit by using a ViewModel in your code.

Get index of clicked row from a nested listview - UWP

I have a nested listview. When clicks on it's button I want to get it's row index. Now I got the index as -1 always.
<ListView x:Name="Mainlist" HorizontalAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:CurrentBooksList">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" ></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListView x:Name="sublist1" ItemsSource="{x:Bind CurrentFoldersArray}" Grid.Column="0" BorderBrush="Black" BorderThickness="0,0,0,1" HorizontalAlignment="Stretch" >
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:CurrentFoldersList">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Foreground="Black" FontWeight="Bold" Grid.Column="0" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center" Text="{x:Bind BookCode}"/>
<ListView x:Name="sublist2" ItemsSource="{x:Bind CurrentBookArray1}" Grid.Column="1" HorizontalAlignment="Stretch" >
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:CurrentBusList1">
<Button Width="120" Height="40" Text="{x:Bind Lockbook}" Click="LockMyBook_btn_Click">
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
private void LockMyBook_btn_Click(object sender, RoutedEventArgs e)
{
int selectedIndx = Mainlist.SelectedIndex; //always return -1
}
How can I get the row number of mainlist view, when user clicks on it's inner listview's button?
Yes it runs well. By getting row number of mainlist, I want to access corresponding items in CurrentBooksList
Well, it's strange. If you're using a basic UWP button control, it doesn't have Text property, your code should not work. But I didn't care about this point, your question was not related to it.
In your button's click event handler, you could get its DataContext, then you could convert it to an CurrentBusList1 object. By this CurrentBusList1 object, you could do some judgements from your 'Mainlist' ItemsSource and get the selected CurrentBooksList object.
public sealed partial class MainPage : Page
{
public ObservableCollection<CurrentBooksList> currentBooksLists { get; set; }
public MainPage()
{
this.InitializeComponent();
currentBooksLists = new ObservableCollection<CurrentBooksList>();
ObservableCollection<CurrentFoldersList> currentFoldersLists = new ObservableCollection<CurrentFoldersList>();
currentFoldersLists.Add(new CurrentFoldersList() { BookCode = "abc123", CurrentBookArray1 = new ObservableCollection<CurrentBusList1>() { new CurrentBusList1() { Lockbook = "123abc" }, new CurrentBusList1() { Lockbook = "123def" } } });
currentFoldersLists.Add(new CurrentFoldersList() { BookCode = "def456", CurrentBookArray1 = new ObservableCollection<CurrentBusList1>() { new CurrentBusList1() { Lockbook = "def456" } } });
currentBooksLists.Add(new CurrentBooksList() { CurrentFoldersArray = currentFoldersLists });
}
private void Button_Click(object sender, RoutedEventArgs e)
{
int index;
var subSelectedItem = ((FrameworkElement)sender).DataContext as CurrentBusList1;
foreach (var item in currentBooksLists)
{
foreach (var folder in item.CurrentFoldersArray)
{
foreach (var bus in folder.CurrentBookArray1)
{
if (bus == subSelectedItem)
{
index = currentBooksLists.IndexOf(item);
break;
}
}
}
}
}
}
public class CurrentBooksList
{
public ObservableCollection<CurrentFoldersList> CurrentFoldersArray { get; set; }
}
public class CurrentFoldersList
{
public string BookCode { get; set; }
public ObservableCollection<CurrentBusList1> CurrentBookArray1 { get; set; }
}
public class CurrentBusList1
{
public string Lockbook { get; set; }
}
<ListView x:Name="Mainlist" HorizontalAlignment="Stretch" ItemsSource="{x:Bind currentBooksLists}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:CurrentBooksList">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" ></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListView x:Name="sublist1" ItemsSource="{x:Bind CurrentFoldersArray}" Grid.Column="0" BorderBrush="Black" BorderThickness="0,0,0,1" HorizontalAlignment="Stretch" >
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:CurrentFoldersList">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Foreground="Black" FontWeight="Bold" Grid.Column="0" TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Center" Text="{x:Bind BookCode}"/>
<ListView x:Name="sublist2" ItemsSource="{x:Bind CurrentBookArray1}" Grid.Column="1" HorizontalAlignment="Stretch" >
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:CurrentBusList1">
<Button Width="120" Height="40" Content="{x:Bind Lockbook}" Click="Button_Click">
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Get index of data binding from ItemsControl

Is there any way to get the index of the data which is binded to the ItemsControl, when a button is clicked?
<ItemsControl x:Name="ic_schranke" DataContext="{Binding Schranke}" >
<ItemsControl.ItemTemplate>
<DataTemplate >
<Button Height="80" x:Name="btn_open" Click="btn_open_Click" HorizontalAlignment="Stretch" Style="{StaticResource TransparentStyle}">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="30*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Rectangle x:Name="rect" Grid.ColumnSpan="3" Grid.Row="1" Opacity="0.65" Grid.Column="0" Fill="#FFCEEAFF"/>
<Border CornerRadius="25" Height="50" Width="50" HorizontalAlignment="Center" Grid.Column="0" Grid.Row="1">
<Border.Background>
<ImageBrush ImageSource="/Assets/schranken.jpg" />
</Border.Background>
</Border>
<TextBlock Name="schranken_name" Grid.Column="1" Grid.Row="1" Text="{Binding name}" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="ExtraLight" Foreground="Black" />
</Grid>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the sample data which is binded to the IC:
List<Schranke> elements;
public UserPage()
{
this.InitializeComponent();
elements = new List<Schranke>();
for (var i = 1; i < 15; i++)
elements.Add(new Schranke() { name = $"Schranke NR:{i}" });
this.ic_schranke.ItemsSource = elements;
}
And here is the code in the button click event:
private async void btn_open_Click(object sender, RoutedEventArgs e)
{
Grid grid = (sender as Button).Content as Grid;
//Debug.WriteLine($"content {tb.Text} clicked");
var tmp_background = grid.Background;
grid.Background = new SolidColorBrush(new Windows.UI.Color() { A = 255, R = 78, G = 170, B = 44 });
VibrationDevice testVibrationDevice = VibrationDevice.GetDefault();
testVibrationDevice.Vibrate(System.TimeSpan.FromMilliseconds(150));
await Task.Delay(3000);
grid.Background = tmp_background;
}
Maybe something along the lines of this:
the presenter - put this in the data context
public class SchrankePresenter : INotifyPropertyChanged
{
private List<Schranke> _elements;
public List<Schranke> Elements
{
get { return _elements; }
set
{
_elements = value;
OnPropertyChanged("Elements");
}
}
public ICommand ClickCommand { get; set; }
private void OnPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
public SchrankePresenter()
{
var elements = new List<Schranke>();
for (var i = 1; i < 15; i++)
elements.Add(new Schranke() { Name = $"Schranke NR:{i}" });
Elements = elements;
ClickCommand = new DelegateCommand(ClickAction);
}
public void ClickAction(Schranke item)
{
VibrationDevice.GetDefault().Vibrate(TimeSpan.FromMilliseconds(150));
}
}
public class Schranke
{
public string Name { get; set; }
}
the template:
<ListView ItemsSource="{Binding Elements}">
<ListView.ItemTemplate>
<DataTemplate>
<Button Height="80"
HorizontalAlignment="Stretch"
Command="{Binding ClickCommand}"
Style="{StaticResource TransparentStyle}">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="30*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Rectangle x:Name="rect"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
Fill="#FFCEEAFF"
Opacity="0.65" />
<Border Grid.Row="1"
Grid.Column="0"
Width="50"
Height="50"
HorizontalAlignment="Center"
CornerRadius="25">
<Border.Background>
<ImageBrush ImageSource="/Assets/schranken.jpg" />
</Border.Background>
</Border>
<TextBlock Name="schranken_name"
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="ExtraLight"
Foreground="Black"
Text="{Binding Name}" />
</Grid>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
you can do the background animation using a storyboard/visualstate
EDIT : regarding the DelegateCommand, it's a simple implementation I use for my WPF apps -
internal class DelegateCommand<T> : ICommand
{
private Action<T> _clickAction;
public DelegateCommand(Action<T> clickAction)
{
_clickAction = clickAction;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_clickAction((T)parameter);
}
}

Categories