I have a listbox containing checkboxes. I want to get all the checkbox checked items content in a string array. How can I get this?
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox x:Name="TrackingView1" Margin="9,0,2,5">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox x:Name="fileNameLinkButton" Content="{Binding BindsDirectlyToSource=True}" FontFamily="Segoe WP Semibold" Foreground="Black" FontSize="20" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
You can use VisualTreeHelper to retrieve the datatemplate items,
use this method to get the first item of datatemplate
//method for finding first element of the listbox data template
private T FindFirstElementInVisualTree<T>(DependencyObject parentElement) where T : DependencyObject
{
var count = VisualTreeHelper.GetChildrenCount(parentElement);
if (count == 0)
return null;
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parentElement, i);
if (child != null && child is T)
{ return (T)child; }
else
{
var result = FindFirstElementInVisualTree<T>(child);
if (result != null)
return result;
}
}
return null;
}
then use this code in the event you want to, for example on a button click
int itemsCount = this.TrackingView1.Items.Count;
List<string> myList = new List<string>();
for (int i = 0; i < itemsCount; i++)
{
ListBoxItem item = (ListBoxItem)this.TrackingView1.ItemContainerGenerator.ContainerFromIndex(i);
CheckBox tagregCheckBox = FindFirstElementInVisualTree<CheckBox>(item);
if((bool)tagregCheckBox.IsChecked)
myList.Add(tagregCheckBox.Content.ToString());
}
In a classic TextBook approach, You could bind "TrackingView1" to a IList, where PersonClass looks some thing like
public class PersonClass
{
public string Name { get; set; }
public bool IsSelected { get; set; }
public PersonClass(string name, bool isSelected)
{
this.Name = name;
this.IsSelected = isSelected;
}
}
and at the point where you want to collect your data,
string[] checkedNames = (from name in Names
where name.IsSelected
select name.Name).ToArray();
I would actually avoid the ToArray(), and accessing the UI directly
Related
I have a CollectionView with DateTime objects inside it. I would like to insert a Label inside the CollectionView (probably using grouping, I think that's the right way) whenever scrolling through the CollectionView I find an element where the DateTime.Month property has changed
<CollectionView
HeightRequest="45">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid BackgroundColor="Black">
<Label Text="{Binding Month}" FontSize="7" TextColor="White" HorizontalTextAlignment="Center"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
UPDATE:
in c#:
public class HumorGroup : ObservableCollection<HumorDiary>
{
public string Name { get; private set; }
public HumorGroup(string name, ObservableCollection<HumorDiary> icon) : base(icon)
{
Name = name;
}
}
public ObservableCollection<HumorGroup> TotHumor { get; private set; } = new ObservableCollection<HumorGroup>();
int count = 0;
foreach(HumorDiary hd in PagineDiario)
{
if(hd.Dt.Date.Month!= count)
{
count = hd.Dt.Date.Month;
TotHumor.Add(new HumorGroup(hd.Dt.Date.Month.ToString(), new ObservableCollection<HumorDiary>
{
}));
}
}
I tried but I don't think I understand the logic. In my test I check that the Month property is different, if so, I create a new group, but when the If control is true, I would like to add all the elements within that group, while as I did it doesn't. how could i solve?
create a new group when the month changes
int month = 0;
HumorGroup group = null;
foreach(HumorDiary hd in PagineDiario)
{
if(group == null || hd.Dt.Date.Month != count)
{
if (group != null) TotHumor.Add(group);
// create a new group
count = hd.Dt.Date.Month;
group = new HumorGroup(count.ToString(), new ObservableCollection<HumorDiary>();
}
group.Add(hd);
}
I managed to access control in the datatemplate of a GridViewItem, the following code:
private void btnChangePhoneNumber_Click(object sender, RoutedEventArgs e)
{
GridCell.SelectedItem = GridCell.Items[3];
var container = GridCell.ContainerFromIndex(3);
var _children = AllChildren(container);
var _control = _children.First(c => c.Name == "PhoneNumber");
_control.text = "123456789";
}
public List<TextBlock> AllChildrenText(DependencyObject parent)
{
var _List = new List<TextBlock> { };
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var _Child = VisualTreeHelper.GetChild(parent, i);
if (_Child is TextBlock)
{
_List.Add(_Child as TextBlock);
}
_List.AddRange(AllChildrenText(_Child));
}
return _List;
}
where the GridCell is a Gridview.
This work.. but..
If I implement GridView with less than 40 items it's all right.
Unlike if I implement gridView with 10000 items, the text change that happens with the method: btnChangePhoneNumber_Click, also happens in other items ... and I can not understand the reason since, in the btnChangePhoneNumber_Click method, only one item is chosen.
Thanks in advance. A greeting.
I have tested your code, but I could not reproduce your issue in my side. As far as I'm concerned, It is low performance to render 10000 items in your GridView. And using VisualTreeHelper will bring about worse performance. You could bind
the text of TextBlock in the datatemplate with mvvm ViewModel. You just need
to modify the view model and the text of TextBlock will be changed. For more please refer to Data binding in depth. And the following is segment code of ViewModel.
MainPageViewModel.cs
public class MainPageViewModel : ViewModelBase
{
private ObservableCollection<Phone> _items;
public ObservableCollection<Phone> Items
{
get
{
return _items;
}
set
{
_items = value;
OnPropertyChanged();
}
}
public MainPageViewModel()
{
var list = new ObservableCollection<Phone>();
for (var i = 0; i < 1000; i++)
{
list.Add(new Phone { PhoneNumber = "123456" });
}
_items = list;
}
}
MainPage.xaml
<Page.DataContext>
<local:MainPageViewModel x:Name="ViewModel"/>
</Page.DataContext>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Click="btnChangePhoneNumber_Click" Content=" click me"/>
<GridView x:Name="GridCell" Height="400" ItemsSource="{Binding Items}" >
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:Phone">
<TextBlock Text="{x:Bind PhoneNumber ,Mode=OneWay}"/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</StackPanel>
I have upload the code sample to github. Please check!
I've got this code. I need to have access to the ScheduleList from my c# code. But it's inaccessible. I can get access to SchedulePivot only.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Margin="0,0,0,50">
<Pivot x:Name="SchedulePivot" Margin="10,10,10,0" Title="Pivot" VerticalAlignment="Top">
<Pivot.ItemTemplate>
<DataTemplate>
<ListBox x:Name="ScheduleList" Margin="0,0,0,17" Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="52" Width="auto">
Searching on StackOverflow I have found this code:
private DependencyObject FindChildControl<T>(DependencyObject control, string ctrlName)
{
int childNumber = VisualTreeHelper.GetChildrenCount(control);
for (int i = 0; i < childNumber; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(control, i);
FrameworkElement fe = child as FrameworkElement;
// Not a framework element or is null
if (fe == null) return null;
if (child is T && fe.Name == ctrlName)
{
// Found the control so return
return child;
}
else
{
// Not found it - search children
DependencyObject nextLevel = FindChildControl<T>(child, ctrlName);
if (nextLevel != null)
return nextLevel;
}
}
return null;
}
I use this line to get the child:
ListBox listCont = FindChildControl<ListBox>(this, "ScheduleList") as ListBox;
Also I tried doing like this:
ListBox listCont = FindChildControl<ListBox>(SchedulePivot, "ScheduleList") as ListBox;
than I do this:
listCont.Items.Add(items);
And get the exeption as listCont=null. What's wrong I'm doing?
I have tested your code, both of the following code work well in my side and I can get the correct result:
ListBox listCont = FindChildControl<ListBox>(this, "ScheduleList") as ListBox;
ListBox listCont = FindChildControl<ListBox>(SchedulePivot, "ScheduleList") as ListBox;
If we want to access the control by using the VisualTreeHelper, we should make sure that we have not called the above code inside the constructor of the MainPage, or we will get the null result as below. Because the control does not been initialized completely:
In order to get the correct result, we need to call the above code inside the MainPage.Loaded event or Button click event to make sure that control has been initialized completely, after that it should work fine.
The following is my sample, please try to refer to:
In the MainPage.xaml:
<Pivot x:Name="SchedulePivot" ItemsSource="{Binding PivotTestlist}" Margin="10,10,10,0" Title="Pivot" VerticalAlignment="Top">
<Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding header}"></TextBlock>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate>
<ListBox x:Name="ScheduleList" Margin="0,0,0,17" Width="Auto" ItemsSource="{Binding ListBoxTestlist}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="52" Width="auto">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding id}"></TextBlock>
<TextBlock Text="{Binding name}"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</Pivot.ItemTemplate>
</Pivot>
<Button Click="Button_Click" Content="Button"></Button>
In the MainPage.xaml.cs:
public class ListBoxTest
{
public string name { get; set; }
public string id { get; set; }
}
public class PivotTest
{
public List<ListBoxTest> ListBoxTestlist { get; set; }
public string header { get; set; }
}
public sealed partial class MainPage : Page
{
public List<PivotTest> PivotTestlist { get; set; }
public MainPage()
{
this.InitializeComponent();
PivotTestlist = new List<PivotTest>();
PivotTest PivotTest1 = new PivotTest();
PivotTest1.ListBoxTestlist = new List<ListBoxTest>();
PivotTest1.ListBoxTestlist.Add(new ListBoxTest() { name = "name1", id = "id1" });
PivotTest1.ListBoxTestlist.Add(new ListBoxTest() { name = "name2", id = "id2" });
PivotTest1.header = "header1";
PivotTestlist.Add(PivotTest1);
PivotTest PivotTest2 = new PivotTest();
PivotTest2.ListBoxTestlist = new List<ListBoxTest>();
PivotTest2.ListBoxTestlist.Add(new ListBoxTest() { name = "name11", id = "id11" });
PivotTest2.ListBoxTestlist.Add(new ListBoxTest() { name = "name22", id = "id22" });
PivotTest2.header = "header2";
PivotTestlist.Add(PivotTest2);
this.DataContext = this;
}
private DependencyObject FindChildControl<T>(DependencyObject control, string ctrlName)
{
int childNumber = VisualTreeHelper.GetChildrenCount(control);
for (int i = 0; i < childNumber; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(control, i);
FrameworkElement fe = child as FrameworkElement;
// Not a framework element or is null
if (fe == null) return null;
if (child is T && fe.Name == ctrlName)
{
// Found the control so return
return child;
}
else
{
// Not found it - search children
DependencyObject nextLevel = FindChildControl<T>(child, ctrlName);
if (nextLevel != null)
return nextLevel;
}
}
return null;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ListBox listCont = FindChildControl<ListBox>(SchedulePivot, "ScheduleList") as ListBox;
int count = listCont.Items.Count;
}
}
The result:
Declare the Pivot in a storyboard and use the x:Key instead of x:Name.
e.g.
<StoryBoard>
<Pivot x:key="nameIt"/>
</StoryBoard>
private void AccesPivot ()
{ //now you can acces your pivot
}
I am trying to implement a list that contains items of a certain type, a Session. Each Session contains a list that contains the type Note. I want to display these Notes in the list under their respective Session header.
Currently I have tried two different methods. The first way was to use ItemsControls as ControlTemplate for the ListBoxItems. This is what I used in the picture below and it is how I want the list to look like. Each red rectangle shows a Session, the items below the header are the Notes. The problem then is that the selection from the ListBox selects ItemsControls instead of each separate Note.
The other way I tried to implement the list is to give each Note a property of which Session it belongs to in order to use a GroupStyle on the ListBox. If I then set the ItemsSource of the ListBox to a list of Notes instead of Sessions I'll get a list that looks like the picture and that has selection of notes. The problem now is that I want the list to show Sessions that doesn't contain any Notes as well.
Does anyone know what I should use to implement a list with selection and that works the way I have described?
MainWindow.xaml:
<TreeView ItemsSource="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Session}" ItemsSource="{Binding Path=Notes}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Note}">
<Expander Header="{Binding Path=Notek}">
<TextBlock Foreground="Red" Text="{Binding Path=Details}" />
</Expander>
</DataTemplate>
</TreeView.Resources>
</TreeView>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Session> sessions = new List<Session>();
for (int i = 0; i < 5; i++)
{
List<Note> notes = new List<Note>();
for (int j = i * 5; j < (i + 1) * 5; j++)
{
Note note = new Note()
{
Notek = string.Format("Note {0}", j),
Details = string.Format("Note j = {0}{1}j*j = {2}", j, System.Environment.NewLine, j*j)
};
notes.Add(note);
}
Session session = new Session()
{
Name = string.Format("Session # {0}", i),
Notes = notes
};
sessions.Add(session);
}
DataContext = sessions;
}
}
public class Session
{
public string Name { get; set; }
public List<Note> Notes { get; set; }
}
public class Note
{
public string Notek { get; set; }
public string Details { get; set; }
}
I think that you can style your HierarchicalDataTemplate as you want. I just show you the example. I think its easier rather than ItemsControl with event handlers.
To create the answer I will assume the following data model:
class Session
{
public IEnumerable<Note> Notes { get; }
}
class Note { }
This requires some coding to sync up the list boxes. I have created an attached property called 'ListBoxGroup'. All listboxes with the same group name can only have a single shared selected item. It is quite a lot of code so it's at the bottom.
Important to note: The listboxgroup for a listbox cannot be changed after originally set, and it doesn't support removal of items, doesn't check for nulls etc. So if you need to change sessions at runtime you should remove items from their groups, check if a listbox is removed from the visual tree, etc.
First the XAML for the page:
xmlns:local="clr-namespace:YourApplication.YourNamespace"
<!-- ItemsControl does not have selection -->
<ItemsControl ItemsSource="{Binding SessionList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- Header for the session -->
<Border Background="Gray">
<TextBlock Text="{Binding Name}" />
</Border>
<!-- listbox for notes -->
<ListBox ItemsSource="{Binding Notes}" local:ListBoxGroup.GroupName="Group1">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- Template for a single note -->
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Below is C# code for the ListBoxGroup property:
public static class ListBoxGroup
{
public static string GetGroupName(DependencyObject obj)
{
return (string)obj.GetValue(GroupNameProperty);
}
public static void SetGroupName(DependencyObject obj, string value)
{
obj.SetValue(GroupNameProperty, value);
}
// Using a DependencyProperty as the backing store for GroupName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty GroupNameProperty =
DependencyProperty.RegisterAttached("GroupName", typeof(string), typeof(ListBoxGroup), new UIPropertyMetadata(null, ListBoxGroupChanged));
private static Dictionary<string, List<ListBox>> _listBoxes = new Dictionary<string, List<ListBox>>();
private static void ListBoxGroupChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
string newValue = e.NewValue as string;
ListBox listBox = obj as ListBox;
if (newValue == null || listBox == null) return;
if (_listBoxes.ContainsKey(newValue))
{
_listBoxes[newValue].Add(listBox);
}
else
{
_listBoxes.Add(newValue, new List<ListBox>() { listBox });
}
listBox.SelectionChanged += new SelectionChangedEventHandler(listBox_SelectionChanged);
listBox.PreviewKeyUp += new System.Windows.Input.KeyEventHandler(listBox_KeyUp);
}
static void listBox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
ListBox listBox = sender as ListBox;
if (e.Key == System.Windows.Input.Key.Up && listBox.SelectedIndex == 0)
{
//move to previous
string groupName = GetGroupName(listBox);
List<ListBox> group = _listBoxes[groupName];
int senderIndex = group.IndexOf(listBox);
if (senderIndex != 0)
{
listBox.SelectedItem = null;
ListBox beforeSender = group[senderIndex - 1];
int index = beforeSender.Items.Count - 1;
beforeSender.SelectedIndex = index;
var container = beforeSender.ItemContainerGenerator.ContainerFromIndex(index);
(container as FrameworkElement).Focus();
}
}
else if (e.Key == System.Windows.Input.Key.Down
&& listBox.SelectedIndex == listBox.Items.Count - 1)
{
//move to next
string groupName = GetGroupName(listBox);
List<ListBox> group = _listBoxes[groupName];
int senderIndex = group.IndexOf(listBox);
if (senderIndex != group.Count - 1)
{
listBox.SelectedItem = null;
ListBox afterSender = group[senderIndex + 1];
afterSender.SelectedIndex = 0;
var container = afterSender.ItemContainerGenerator.ContainerFromIndex(0);
(container as FrameworkElement).Focus();
}
}
}
static void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
ListBox listBox = sender as ListBox;
string groupName = GetGroupName(listBox);
foreach (var item in _listBoxes[groupName])
{
if (item != listBox)
{
item.SelectedItem = null;
}
}
}
}
}
My code is as below.
<ListBox x:Name="lstBoxMarket" BorderThickness="0" Height="Auto" HorizontalAlignment="Center" Width="200" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
<ListBox.ItemTemplate>
<HierarchicalDataTemplate>
<CheckBox IsChecked="{Binding Checked}" CommandParameter="{Binding MarketId}" Tag="{Binding MarketId}" Content="{Binding Market}" Foreground="#FF3D66BE" Name="chkMarket"/>
</HierarchicalDataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I want to access the selected and deselected checkboxes in the list on click of save button . I am unable to access chkMarket straight away. Can anyone help?
Starting from your code I tried something like that
// find all T in the VisualTree
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject parent)
where T : DependencyObject
{
List<T> foundChilds = new List<T>();
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
T childType = child as T;
if (childType == null)
{
foreach(var other in FindVisualChildren<T>(child))
yield return other;
}
else
{
yield return (T)child;
}
}
}
Then in your MainWindow
private void button1_Click(object sender, RoutedEventArgs e)
{
// find all checkboxes in my window
IEnumerable<CheckBox> myBoxes = FindVisualChildren<CheckBox>(this);
int numChecked = 0;
foreach(CheckBox cb in myBoxes)
{
if(cb.Name != "chkMarket")
continue;
if (cb.IsChecked == true)
numChecked++;
}
MessageBox.Show("Checked items = " + numChecked);
}
My viewmodel code is
public class ViewModel
{
public ViewModel()
{
_persons = new ObservableCollection<Person>();
_persons.Add(new Person() { Name = "Paul", Checked = false });
_persons.Add(new Person() { Name = "Brian", Checked = true });
}
private ObservableCollection<Person> _persons;
public ObservableCollection<Person> Persons
{
get { return _persons; }
}
}
public class Person
{
public String Name { get; set; }
public Boolean Checked { get; set; }
}
You should be able to see the message "Checked items=1".
Hope this helps
Since it was 2 way binding i could access the values selected by the checkboxes from the item source of listbox.
DataTable lstBoxMarketItemSourceDT = ((DataView)lstBoxMarket.ItemsSource).ToTable();
"Checked" column in the data table retrieved gives the updated check box values.