I have a ListView with GroupStyle in UWP like in the example here. I have the following HeaderTemplate:
<GroupStyle.HeaderTemplate>
<DataTemplate x:DataType="data:GroupInfoList">
<Grid Tapped="Header_Tapped">
<TextBlock Text="{x:Bind Key}" />
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
and I can get the selected Key by
private void Header_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
var selItem = (sender as Grid).DataContext as GroupInfoList;
var selKey = selItem.Key;
}
Now my problem is that despite knowing the selected Key, I cannot access the Items from it. In Debug, I can see the Count Property and it is equal to the number of elements, that are inside the Group, but I do not know, how to iterate through it.
I would very much like to iterate through all Items, that have the same Key as selKey and set a boolean property called _isVisible for all those items. What is a good/fast/effective way to accomplish this?
GroupInfoList is derived from List<object>. It contains all the Contact with the same Key. When you get selItem, you can use following code to iterate through it like iterate through a List.
foreach (var item in selItem)
{
var contact = item as Contact;
//suppose you have add a _isVisible property in Contact
contact._isVisible = true;
}
Related
I have a ListBox in xaml which has a sub-ListBox inside the top-level ListBox's item template. Because the sub-ListBox is multi-select, and I can't bind the SelectedItems of the sub-ListBox to a viewmodel property for some reason, I'm trying to do a lot of this in the view code-behind.
I have everything working, except for one snag: I want to select all items in each sub-ListBox by default. Since the SelectedItems aren't data-bound, I'm trying to do it manually in code whenever a SelectionChanged event fires on the top-level ListBox. The problem is that I don't know how to get from the top-level ListBox to the sub-ListBox of the top-level selected item. I think I need to use the visual tree, but I don't know how to even get the dependency object that corresponds to the selected item.
Here's the code:
<ListBox ItemsSource="{Binding Path=Stuff}" SelectionChanged="StuffListBox_SelectionChanged" SelectedItem="{Binding Path=SelectedStuff, Mode=TwoWay}" telerik:RadDockPanel.Dock="Bottom">
<ListBox.ItemTemplate>
<DataTemplate>
<telerik:RadDockPanel>
<TextBlock Text="{Binding Path=Name}" telerik:RadDockPanel.Dock="Top" />
<ListBox ItemsSource="{Binding Path=SubStuff}" SelectionMode="Multiple" SelectionChanged="SubStuffListBox_SelectionChanged" Visibility="{Binding Converter={StaticResource StuffToSubStuffVisibilityConverter}}" telerik:RadDockPanel.Dock="Bottom">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</telerik:RadDockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The converter ensures that only the selected top-level item has a visible sub-ListBox, and that's working.
I need to implement the following method:
private void StuffListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox stuffListBox = (ListBox)sender;
foreach (Stuff stuff in e.AddedItems)
{
...
subStuffListBox.SelectAll();
}
}
I tried doing stuffListBox.ItemContainerGenerator.ContainerFromItem(stuff), but that always returns null. Even stuffListBox.ItemContainerGenerator.ContainerFromIndex(0) always returns null.
I also get strange behaviour from the selection changed method. 'e.AddedItems will contain items, but stuffListBox.SelectedItem is always null. Am I missing something?
From what I've read, my problem is coming from the fact that the containers haven't been generated at the time that I'm getting a selection change event. I've seen workarounds that involve listening for a the item container generator's status changed event, but I'm working in Silverlight and don't have access to that event. Is what I'm doing just not possible in Silverlight due to the oversight of making SelectedItems on a ListBox read-only?
Like you say this is probably best done in the ViewModel but you can select all the sub list items in code behind using VisualTreeHelper.
private void StuffListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var stuffListBox = (ListBox)sender;
ListBoxItem item = (ListBoxItem)stuffListBox.ContainerFromItem(stuffListBox.SelectedItem);
ListBox sublist = FindVisualChild<ListBox>(item);
sublist.SelectAll();
}
FindVisualChild Method as per MSDN
private childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
return (childItem)child;
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
I'm building an app where there's a arbitrarily long list of items where you can click on any of them and edit in place. As you edit any of these items I want to programmatically change focus to the next item in the list.
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<phone:LongListSelector x:Name="MainLongListSelector" Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="MainLongListSelector_SelectionChanged">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,-10,0,-12">
<TextBox x:Name ="tb" Text="{Binding TheText}" TextWrapping="Wrap" TextChanged="TextBox_TextChanged" />
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
As the user types in the first TextBox and his text exceeds a certain # of characters (e.g. 138) I want to either add another item to the list as the next item and change focus to it, or, if there's already a next item, change focus to it.
I can't figure out how to get access to
1) The root list box
2) The TextBox control within an item given a list box item ID
Here's what I tried. When this runs, the MainLongListSelector.SelectedITem = nextItem causes the next item to be selected, but it does NOT get focus.
private void TextBox_TextChanged(object sender, TextChangedEventArgs e) {
var editBox = sender as TextBox;
var selectedItem = MainLongListSelector.SelectedItem as ItemViewModel;
if (editBox != null && selectedItem != null && editBox.Text.Length > 138) {
// Move data at end to next box
var overflow = editBox.Text.Substring(138, editBox.Text.Length - 138) ;
selectedItem.Tweet = editBox.Text.Substring(0, 138);
var nextItem = App.ViewModel.Items[int.Parse(selectedItem.ID) + 1];
nextItem.Tweet = overflow;
MainLongListSelector.SelectedItem = nextItem;
}
}
I want to be able to access the actual TextBox of that nextItem so I can explicitly set focus to it.
The same question applies if I just use ListBox but the issues are different. In the ListBox case when the DataTemplate contains a TextBox and focus is set, I don't get SelectionChanged events...which is why I'm sticking with LongListSelector.
If I understand you correctly, you want to the TextBox get focus when the item item selected.
If this is what you need, here is a way to bind LisBoxItem.IsSelected To Element.Focus():
http://social.msdn.microsoft.com/Forums/en-US/adeb3e7f-16df-4c7b-b2d2-d7cdedb32ac0/setting-focus-of-a-textbox-inside-a-listbox?forum=wpf
The following scenario:
I use a gridview to present grouped data.
I added a TextBlock to the headertemplate which
should contain the number of items in this group. (For example)
( Edit: In my scenario i show always 6 items and want to show the overflow in the TextBlock children of my HeaderTemplate )
How can i access the individual group headers from code to manipulate this TextBlock?
Here is an example of the result:
And here a simplified example of my GroupHeaderTemplate:
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock x:name="overflow"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
So i want to access and manipulate the "overflow" item individually for each generated group!
Here's what you really want.
First edit the GroupStyle HeaderTemplate
<GridView.GroupStyle>
<GroupStyle HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Width="500" Margin="5,0,0,5">
<TextBlock HorizontalAlignment="Left">
<Run Text="{Binding Name}" />
<Run FontFamily="Segoe Ui Symbol" Text="" />
</TextBlock>
<TextBlock HorizontalAlignment="Right">
<Run Text="{Binding Children.Count, FallbackValue=0}" />
<Run Text="Items" />
</TextBlock>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid />
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>
please note I am using a VaraibleSizedWrapGrid
Next, handle the GridView's Loaded Event
class SubtractConverter : IValueConverter
{
public double Amount { get; set; }
public object Convert(object v, Type t, object p, string l)
{ return System.Convert.ToDouble(v) - Amount; }
public object ConvertBack(object v, Type t, object p, string l)
{ throw new NotImplementedException(); }
}
private void GridView_OnLayoutUpdated(object sender, RoutedEventArgs e)
{
var grid = sender as GridView;
var converter = new SubtractConverter { Amount = 5 * 2 /* padding x2 */ };
foreach (GroupItem group in (grid.ItemsPanelRoot as Panel).Children)
{
var result = VisualTreeHelper.GetChild(group, 0);
while (!(result is Grid))
result = VisualTreeHelper.GetChild(result, 0);
var items = (result as Panel).Children.OfType<ItemsControl>()
.First().ItemsPanelRoot;
var binding = new Binding
{
Path = new PropertyPath("ActualWidth"),
Mode = BindingMode.OneWay,
Converter = converter,
Source = items,
};
var header = (result as Panel).Children.OfType<ContentControl>()
.First().ContentTemplateRoot as FrameworkElement;
header.SetBinding(FrameworkElement.WidthProperty, binding);
}
}
And, presto! Now your header is perfectly sized to the width of the items in the group.
Things to remember (as the designer):
Your grouped items might be as narrow as a single column. Solve this with TextTrimming in the Header TextBoxes.
Your grouped items might be wider than the monitor and # items might be off-screen. Solve this with a MinWidth on the containing grid.
Best of luck!
I finally managed to get my hands on the desired Element by using the VisualTreeHelperExtensions
First of all you have to install XamlToolkit via Nuget
Afterwards add a using directive for the Extensions:
using WinRTXamlToolkit.Controls.Extensions;
Now you can use several more methods on your ui elements , one of them is getDescendantsByType()
which i use to get all textblock elements hold by my gridview. I added a tag to my overflow textblocks which i check for when iterating through the gridviews descendants, see for yourself:
private void ItemsGridView_Loaded(object sender, RoutedEventArgs e)
{
foreach (TextBlock element in this.myGridView.GetDescendantsOfType<TextBlock>())
{
if(element.Tag != null && element.Tag.Equals("itemCountBlock")){
element.Text = "Finally solved!";
}
}
}
This should work out for any ui element and any property one would wanna change.
I'm having a problem with getting a string from a bound textblock within a listbox, when I use the code below, I can bind the listbox and the listbox has items showing up, but when the item in the list is clicked I don't get the proper string, I print a message box a message with objects names like
"MyApp.Item"
shows up instead. myApp is the name of the app and Item is the name of my model that I am binding to the listbox. The proper text from the selected item showed up when the listbox was not binded.
private void listBoxtrend_Tap(object sender, GestureEventArgs e)
{
selectedText = "";
selectedText = listBox.SelectedValue.ToString();
MessageBox.Show(selectedText);
}
xml
<ListBox ItemsSource="{Binding Item}" Foreground="RoyalBlue"
Height="395" HorizontalAlignment="Center"
Margin="12,111,0,0" Name="listBox"
VerticalAlignment="Top" Width="438"
TabIndex="10" Tap="listBox_Tap" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock TextWrapping="Wrap" FontSize="26" HorizontalAlignment="Left"
Name="tblItem" Text="{Binding ItemString}"
VerticalAlignment="Top" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'd really appreciate if you could help me thanks
You're binding to the ItemString in the DataTemplate's TextBlock and the Item Collection in the ListView. As such the SelectedValue will be of the Item type. You should actually be doing something like this in your Tap handler to get at the ItemString's value...
private void listBoxtrend_Tap(object sender, GestureEventArgs e)
{
selectedText = "";
var selected = listBox.SelectedValue as Item;
selectedText = selected.ItemString;
MessageBox.Show(selectedText);
}
In your example, the ToString is printing the name of the class. You could also override ToString in your Item model to be whatever you want the string to be.
Note: the types and such may be a bit off, I guessed a bit based off of what you wrote in your question. Also, there is no need to set selectedText to an empty string that will just be overwritten in the third line above. I wanted to keep it so you could get some idea of what I changed in your code.
It's very simple, try following:
string selectedText = ListBox.GetItemText(ListBox.SelectedItem);
You need to also set the SelectedItem of the Listbox to something.
SelectedItem = {Binding SelectedItem}
and rename your ItemsSource to "Items" as that makes more sense.
Your SelectedItem in your codebehind or your ViewModel should then contain a property:
public class Item
{
public string ItemString { get;set; }
}
Try This...
string ListBoxConent = ((ListBoxItem)listbox.SelectedItem).Content.ToString();
Try
listBox.SelectedItem.ToString()
If a property isn't specified in ValueMember then SelectedValue returns the results of the ToString method of the object.
I am building a small Windows Phone application which has a databound ListBox as a main control. DataTemplate of that ListBox is a databound ItemsControl element, which shows when a person taps on a ListBox element.
Currently, I am accessing it by traversing the visual tree of the application and referencing it in a list, and than getting the selected item through SelectedIndex property.
Is there a better or more effective way?
This one works currently, but I am afraid if it would stay effective in case of larger lists.
Thanks
Have you tried wiring the SelectionChanged event of the ListBox?
<ListBox ItemsSource="{Binding}" SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- ... -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
With this in the code behind:
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox listBox = sender as ListBox;
// nothing selected? ignore
if (listBox.SelectedIndex != -1)
{
// something is selected
}
// unselect the item so if they press it again, it takes the selection
listBox.SelectedIndex = -1;
}
ListBoxItem item = this.lstItems.ItemContainerGenerator.ContainerFromIndex(yourIndex) as ListBoxItem;
Then you can use the VisualTreeHelper class to get the sub items
var containerBorder = VisualTreeHelper.GetChild(item, 0) as Border;
var contentControl = VisualTreeHelper.GetChild(containerBorder, 0);
var contentPresenter = VisualTreeHelper.GetChild(contentControl, 0);
var stackPanel = VisualTreeHelper.GetChild(contentPresenter, 0) as StackPanel; // Here the UIElement root type of your item template, say a stack panel for example.
var lblLineOne = stackPanel.Children[0] as TextBlock; // Child of stack panel
lblLineOne.Text = "Some Text"; // Updating the text.
Another option is to use services of the GestureServices class available in the WP7 Toolkit.
You'll need to add a GestureListner to the Root Element of your DataTemplate like so:
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Controls:GestureService.GestureListener>
<Controls:GestureListener Tap="GestureListener_Tap" />
</Controls:GestureService.GestureListener>
<TextBlock x:Name="lblLineOne" Text="{Binding LineOne}" />
<TextBlock Text="{Binding LineTwo}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
And in the GestureListener_Tap event handler, you use this snippet.
private void GestureListener_Tap(object sender, GestureEventArgs e)
{
var itemTemplateRoot = sender as StackPanel;
var lbl1 = itemTemplateRoot.Children[0] as TextBlock;
MessageBox.Show(lbl1.Text);
}
I'm not sure how the GestureListner recognize internally the item being tapped but I guess that it uses the VisualTreeHelper, at least this method is more concise.