How to focus item in listbox from view model - wpf

I make autocomplete and I want go throught with keys up and down in listbox with results.
Before that I have to focus the first item of listbox from textbox where I write the text.
<TextBox Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Name="Client" Text="{Binding Client}" cal:Message.Attach="[Event KeyUp] = [Action ExecuteFilterView($executionContext)]" Validation.ErrorTemplate="{x:Null}" >
<TextBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding UserCanChooseClient}" Value="True">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=Clients}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<ListBox x:Name="Clients" Width="190" Height="auto" MaxHeight="400" Margin="5 28 0 0" cal:Message.Attach="[Event KeyUp] = [Action ExecuteClientsView($executionContext)]; [Event MouseLeftButtonUp]=[Action HandleClientChosenClick($eventArgs)]" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden" SelectedItem="{Binding Path=SelectedClient}" ItemsSource="{Binding Path=Clients}" DisplayMemberPath="Description" SelectedValuePath="Code"></ListBox>
But the focusmanager focus the listbox, not the first item. I have to press button down twice to start to walk throught the list. Changing property SelectedClient in listbox didnt help.

on the event SelectionChanged or other event sent by listbox:
cal:Message.Attach="[Event SelectionChanged] = [Action LstBox_OnSelectionChanged($source, $eventArgs]"
public void LstBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var lstbox = (ListBox) sender;
var item = (ListBoxItem)lstbox.ItemContainerGenerator.ContainerFromItem(lstbox.SelectedItem);
if (item != null)
item.Focus();
}
if you want to have the first item of listbox selected at startup, you could use event Loaded:
cal:Message.Attach="[Event Loaded] = [Action LstBox_OnLoaded($source)]"
public void LstBox_OnLoaded(object sender)
{
var lstbox = (ListBox)sender;
lstbox.SelectedIndex = 0;
var item = (ListBoxItem)lstbox.ItemContainerGenerator.ContainerFromItem(lstbox.SelectedItem);
if (item != null)
item.Focus();
}

Related

Setting SelectedItem in ListView when user clicks in ItemTemplate Textbox

I have the following ListView (simplified):
<ListView Name="lvwNotes" KeyUp="lvwNotes_KeyUp">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<DockPanel Background="LightGray">
<TextBlock DockPanel.Dock="Right" Text="{Binding Path=Author}" />
<TextBlock Text="{Binding Path=Timestamp}" />
</DockPanel>
<TextBox Text="{Binding Path=Text}"
GotFocus = "lvwNotes_TextBox_GotFocus"
TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
Changing the selected item through a click only works when the user clicks on the DockPanel with the TextBlocks, but not on clicking the TextBox. What I want to achieve is to set the selected item to that one containing the TextBox into which the user clicked.
I managed to get through to the ListViewItem related to the TextBox:
private void lvwNotes_TextBox_GotFocus(object sender, RoutedEventArgs e) {
DependencyObject o = Tools.GetAncestorByType((DependencyObject)sender, typeof(ListViewItem));
if (!o.Equals(null)) {
// code to select this ListViewItem
}
}
But setting
lvwNotes.SelectedIten = o ;
remains without effect. I've tried also some tricks with Dispatcher.BeginInvoke, but to be honest, I don't exactly know what I'm doing there.
Add this to your code
<ListView.Resources>
<Style TargetType="ListViewItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.Resources>
The DataContext unless explicitly changed in the DataTemplate is the current item, hence:
private void lvwNotes_TextBox_GotFocus(object sender, RoutedEventArgs e)
{
var tb = (TextBox)sender;
lvwNotes.SelectedItem = tb.DataContext;
}

Custom templated combobox with special non templated item

I have a RadTreeView, in each item there is a RadCombobox with some elements. Now I need to add some "special" item into each combobox. User can click on this item to add new element in combobox:
My current code:
<DataTemplate x:Key="Monitor">
<Grid Height="Auto" Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Height="16" Width="16" Source="icons\monitor.png" />
<TextBlock Text="{Binding Name}" Margin="5 0 0 0" Grid.Column="1" Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
<!-- PROBLEM IS HERE -->
<telerik:RadComboBox Name="RadComboSchedule"
Grid.Column="2"
Margin="10 0 0 0"
Width="155"
ItemsSource="{Binding Source={StaticResource DataSource}, Path=ScheduleDataSource}"
ItemTemplate="{StaticResource ComboBoxTemplate}"
>
</telerik:RadComboBox>
<Button Name="BtnRemoveMonitor" Grid.Column="3" Style="{StaticResource ButtonListBoxItemStyle}" Template="{StaticResource RemoveButtonTemplate}" />
</Grid>
</DataTemplate>
<HierarchicalDataTemplate x:Key="Group"
ItemTemplate="{StaticResource Monitor}"
ItemsSource="{Binding Monitors}">
<TextBlock Text="{Binding Name}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</HierarchicalDataTemplate>
<telerik:RadTreeView
Name="RadTreeViewGroups"
Height="auto"
Width="auto"
ItemsSource="{Binding Source={StaticResource DataSource}, Path=GroupsDataSource}"
ItemTemplate="{StaticResource Group}"
>
</telerik:RadTreeView>
So, I have all like at a screenshot without element "Add new item".
Any ideas?
PS It's not a problem to use standard WPF Combobox and TreeView controls.
You can create a new item in the DataSource of the ComboBox which name is "ADD NEW ITEM" and handle when the user select it.
private void SelectItem(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems[0].ToString() == "new")
{
string newItem = "completely new item";
dataSource.Add(newItem);
((ComboBox)sender).SelectedItem = newItem;
}
}
In this question you can see a better example that each item is an instance of a class, so it's easier to handle the "add item" request:
Databound WPF ComboBox with 'New...' item
Edit (about the 'add item' button template):
Based on the example above
Having this class
public class DisplayClass
{
public string Name { get; set; }
public bool IsDummy { get; set; }
}
You bind ComboBox.ItemsSource to an ObservableCollection like this one:
public ObservableCollection<DisplayClass> DataSource { get; set; }
Add that "dummy" item to the collection
DataSource.Add(new DisplayClass { Name = "ADD ITEM", IsDummy = true });
Then you handle the item selection with something like this:
private void SelectItem(object sender, SelectionChangedEventArgs e)
{
var comboBox = (ComboBox)sender;
var selectedItem = comboBox.SelectedItem as DisplayClass;
if (selectedItem != null && selectedItem.IsDummy)
{
//Creating the new item
var newItem = new DisplayClass { Name = comboBox.Items.Count.ToString(), IsDummy = false };
//Adding to the datasource
DataSource.Add(newItem);
//Removing and adding the dummy item from the collection, thus it is always the last on the 'list'
DataSource.Remove(selectedItem);
DataSource.Add(selectedItem);
//Select the new item
comboBox.SelectedItem = newItem;
}
}
To display the items properly, you'll need to change the ComboBox.ItemTemplate, making the image invisible when the item is dummy
<ComboBox ItemsSource="{Binding DataSource}" SelectionChanged="SelectItem">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Width="180" />
<Image HorizontalAlignment="Right" Source="..." MouseLeftButtonUp="DeleteItem">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding IsDummy}" Value="True">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

how to access the label inside datatemplate

hello everybody i have a listbox within which is a datatemplate.Inside it is checkbox,textbox,label...Wat i want is to get the value of the label wen the checkbox is unchecked? or any alternative as to how to access the label value but only wen the checkbox is unselected............PLease help me out.
the code is as
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="sp" Orientation="Horizontal" Margin="3,3,3,3" >
<CheckBox Name="chkSubject" IsChecked="{Binding RelativeSource{RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" VerticalAlignment="Center" Margin="0,0,4,0" Unchecked="chkSubject_Unchecked">
<TextBlock FontSize="11" Text="{Binding subject_name}" />
</CheckBox>
<Label Name="lbl_idOfSub" Content="{Binding subject_id}" Visibility="Visible">
</Label>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Since you're using binding on label, I'd go for accessing subject_id from the object the datatemplate is describing. Like this:
var subjectId = dataBoundItem.subject_id;
That's the correct way to go with MVVM and bindings.
UPDATE:
Here's the basic MVVM approach to solving this problem. First of all, I've cleaned up a bit your listbox declaration and added a trigger that sets IsSelected binding:
<ListBox ItemsSource="{Binding}">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="sp" Orientation="Horizontal" Margin="3,3,3,3" >
<CheckBox Name="chkSubject" IsChecked="{Binding IsSelected}" VerticalAlignment="Center" Margin="0,0,4,0" Unchecked="chkSubject_Unchecked_1">
<TextBlock FontSize="11" Text="{Binding SubjectName}" />
</CheckBox>
<Label Name="lbl_idOfSub" Content="{Binding SubjectId}" Visibility="Visible"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here, whenever value IsSelected on individual ListBoxItem changes, the "IsSelected" binding of the viewModel is changed. Here's the model:
public class SelectableItem : INotifyPropertyChanged
{
private string _subjectId;
private bool _isSelected;
private string _subjectName;
public string SubjectId
{
get { return _subjectId; }
set { _subjectId = value; OnPropertyChanged("SubjectId"); }
}
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; OnPropertyChanged("IsSelected"); }
}
public string SubjectName
{
get { return _subjectName; }
set { _subjectName = value; OnPropertyChanged("SubjectName"); }
}
// .. INotifyPropertyChangedImplementation
Your IsSelected will be set to true whenever relevant item is selected and to false whenever it is unselected. You may put your code in to the "set" item of the "IsSelected" property and check (value == false) and execute necessary piece of code as you see fit. This would be MVVM approach to the matter.
Using the event, you can do as follows:
private void chkSubject_Unchecked_1(object sender, RoutedEventArgs e)
{
FrameworkElement control = sender as FrameworkElement;
if (control == null)
return;
SelectableItem item = control.DataContext as SelectableItem;
if (item == null)
return;
string yourValue = item.SubjectId;
}
I strongly recommend you read about MVVM and bindings.
What about using the Checked and UnChecked events of your CheckBox, so that you can retrieve the value of subject_id which is binded to your Label.

DataTrigger on parent property

How can I set the "IsAtLeastOneUserAvailable" dependency property correctly if at least one of my button is available? The dependency property is set in the code of the xaml. So, we can bind to it like that
"{Binding IsAtLeastOneUserAvailable, ElementName=control}"
I want to hide the label if no control is visible in the ItemsControl.
<UserControl ... Name="control">
<Label Content="Test" Visibility={Binding IsAtLeastOneUserAvailable, ElementName=control">
<ItemsControl ItemsSource="{Binding Users, ElementName=control}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Visibility="{Binding IsAvailable, Converter={StaticResource BooleanToVisibilityConverter}}">
<Button.Triggers>
<DataTrigger Binding="IsAvailable" Value="True">
<Setter Property="IsAtLeastOneUserAvailable" Value="True" />
</DataTrigger>
</Button.Triggers>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
Instead of trying to set the value with a trigger, Why not just set your UserControl.IsAtLeastOneUserAvailable property inside your UserControl based on a linq query?
public bool IsAtLeastOneUserAvailable
{
get
{
return Users.Any(p => p.IsAvailable);
}
}
You can also raise this PropertyChanged event in the User's changed event:
public MyUserControl()
{
Users.CollectionChanged += Users_CollectionChanged;
}
void Users_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach(User user in e.NewItems)
user.PropertyChanged += User_PropertyChanged;
if (e.OldItems != null)
foreach(User user in e.OldItems)
user.PropertyChanged -= User_PropertyChanged;
}
void User_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsAvailable")
RaisePropertyChanged("IsAtLeastOneUserAvailable");
}

Wrong selection in ListBox with VirtualizationMode="Recycling" and SeclectionMode="Extended"?

I have a really strage behaviour. I have a ListBox in the View with a DataTemplate for its items including ViewModels. I bind the IsSelected to my ViewModel and use SelectionMode="Extended". Everything works fine.
BUT if I add VirtualiuationMode="Recycling" the I get the wrong items.
To reproduce: select items with Ctrl, then scroll down and select just one item. The normal behaviour deselects all items and just select the last one without holded Ctrl.
But if I check my ViewModel all the old items are selected!?!
<Grid>
<StackPanel>
<ListBox ItemsSource="{Binding People}" MaxHeight="100"
SelectionMode="Extended"
VirtualizationMode="Recycling">
<!--VirtualizingStackPanel.IsVirtualizing="True">-->
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<views:PeopleView />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Click="Button_Click">
OK
</Button>
</StackPanel>
</Grid>
The item template
<UserControl x:Class="WpfApplication1.View.PeopleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="Auto" Width="Auto">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="A"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="B"/>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding Path=Name}"
Name="tbx_Name"
Grid.Column="0"/>
<CheckBox IsChecked="{Binding Path=IstAktiv}"
Name="cbx_IstAktiv"
Grid.Column="1"/>
</Grid>
Any idea?
I got a workaround but why do I have to change it "manually" in the change event and not by databinding?
private void Lbx_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox lbx = (ListBox)sender;
foreach (PersonViewModel item in lbx.Items)
{
item.IsSelected = lbx.SelectedItems.Contains(item);
}
}
Another option related to KCT's earlier answer is to use the AddedItems and RemovedItems from the SelectionChangedEventArgs and target the changes, such as:
private void Lbx_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var item in e.AddedItems)
{
((PersonViewModel)item).IsSelected = true;
}
foreach (var item in e.RemovedItems)
{
((PersonViewModel)item).IsSelected = false;
}
}
This may give better performance with larger collections (I've got about 15,000 entries in a Virtualizing Tile Panel in a ListBox).

Resources