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
Related
I have a list of devices and in that list user will select which COM port represents which device, each device has its own StackPanel shown below:
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Width="140" Text="IMT" VerticalAlignment="Center"/>
<ComboBox Width="250" Margin="0,0,40,0" x:Name="FM_list" SelectionChanged="DeviceSelected"/>
<TextBlock x:Name="FM_selection" Margin="0,0,40,0" Width="80 "/>
<Button Background="Red" Width="50" Click="Port_selected" x:Name="FM_selection1"/>
</StackPanel>
After user makes his selection in the ComboBox it is verified by clicking an adjecent Button.
I'd like it so that when the Button is clicked x:Name of the TextBlock (or an alternate way of referencing) is passed to the Port_selected function so I can get the correct device when calling TextBox.Name on the sender.
I could a seperate x:Name for each of those buttons and a dictionary to match which button matches which TextBlock and which StackPanel, but I'd like to know how to do without that workaround. Right now I just strip the last char of the Button's x:Name...
Port_selected is an event handler and you cannot pass a string value to this one.
If you are familiar with the MVVM design pattern there are better ways to solve this, for example using commands and command parameters, but the easiest solution given your current setup is probably to get a reference to the TextBlock in the event handler and then get the value of its Name property, e.g.:
private void Port_selected(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
StackPanel stackPanel = btn.Parent as StackPanel;
if (stackPanel != null)
{
TextBlock textBlock = stackPanel.Children
.OfType<TextBlock>()
.FirstOrDefault(x => !string.IsNullOrEmpty(x.Name));
if (textBlock != null)
{
string name = textBlock.Name;
//...
}
}
}
I want to populate a ComboBox based on selection of other ComboBox.
Both combo boxes are populate from database using WCF.
My problem is that on first selection it's not working (just after second selection it's work and it's show results from first selection).
XAML
<ComboBox
x:Name="selClientCombo"
SelectionChanged="listEchipamente"
IsEditable="True"
SelectedIndex="-1"
HorizontalAlignment="Left"
Margin="455,35,0,0"
VerticalAlignment="Top"
Width="215"
ItemsSource="{Binding Mode=OneWay}"/>
<ComboBox
x:Name="selEchipamentCombo"
HorizontalAlignment="Left"
Margin="457,65,0,0"
VerticalAlignment="Top"
Width="213"
ItemsSource="{Binding}"/>
code
private void listEchipamente(object sender, SelectionChangedEventArgs e)
{
List<string> echipamenteWCF = client.getEchipament(selClientCombo.Text).ToList();
MessageBox.Show(" Client Selected !");
if (selEchipamentCombo.Items.Count >0)
{
selEchipamentCombo.Items.Clear();
}
for (int i = 0; i < echipamenteWCF.Count(); i++)
{
selEchipamentCombo.Items.Add(echipamenteWCF[i]);
}
}
At the time SelectionChanged is fired, the Text has not been updated (and hence it holds the previous value).
You should access the underlying data item to get the Text instead:
if(selClientCombo.SelectedItem == null) return;
List<string> echipamenteWCF =
client.getEchipament(selClientComo.SelectedItem.ToString()).ToList();
...
I supposed the ToString() will resolve the display Text. You can always cast SelectedItem to the actual type and access its string property (being shown as Text) easily. You can also access the SelectedValue with condition that some SelectedValuePath is set for the ComboBox.
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 have a ListBox called NotesList. I have an ObservableCollection called noteList, and I have a TextBox called NoteContents.
In my ObservableCollection, I set the Filename and Contents properties for a few items and then it gets added (bound) to my ListBox.
But now, I want to (when I click a button), show the "Contents" of the ListBox Item that was selected in the NoteContents TextBox.
How can I do this?
I currently have:
private void NotesList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
NoteContents.Text = noteList.Where(x => x.Filename.Contains(NotesList.SelectedValue.ToString())).FirstOrDefault().Contents;
}
You can do this without button clicks, just binding like:
<ListBox Name="NotesList" ItemsSource="{Binding YourObservableCollection}">
<!--Your bindings here-->
</ListBox>
<TextBox Text="{Binding ElementName=NotesList, Path=SelectedItem.Contents}" />
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.