My usercontrol has Listbox with Images as ListboxItems, here i'm facing an issue when i navigate listbox items (Images) using "Arrow Keys",i could'n navigate the items that is present in Next row, say for example ,List box contains rows of images*("I have used WrapPanel")*, if i navigate an images using Right arrow key i cant able to move to next row,
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="KeyboardNavigation.DirectionalNavigation" Value="Cycle" />
<Setter Property="IsTabStop" Value="True" />
</Style>
</ListBox.ItemContainerStyle>
Based on this answer which almost worked, but not quite.
Put a KeyDown event on your ListBox and use its ItemsCollection to select the next or previous element when you see a Right or Left keypress.
That moves the selection, but not the keyboard focus (dotted line), so you must also call MoveFocus on the element that has Keyboard focus.
private void ListBox_KeyDown( object sender, KeyEventArgs e )
{
var list = sender as ListBox;
switch( e.Key )
{
case Key.Right:
if( !list.Items.MoveCurrentToNext() ) list.Items.MoveCurrentToLast();
break;
case Key.Left:
if( !list.Items.MoveCurrentToPrevious() ) list.Items.MoveCurrentToFirst();
break;
}
e.Handled = true;
if( list.SelectedItem != null )
{
(Keyboard.FocusedElement as UIElement).MoveFocus( new TraversalRequest( FocusNavigationDirection.Next ) );
}
}
Finally, make sure you have IsSynchronizedWithCurrentItem="True" on your ListBox.
This will give you the wrap-around behaviour that you want.
Related
Hi I have a Datagrid that is bound to an ObservableCollection of custom AutoCAD layer objects. 3 of the columns are DataGridTextColumns and work correctly. However I also have a DataGridTemplateColumn that contains a StackPanel containing a label and a Rectangle. I am using the label to display the ACI or RGB value of the layer depending how it is set and displaying the colour in the rectangle. The rectangle has a mouse down event that launches a colour picker dialog so the user can select a new colour for the layer. This functionality works. What doesn't work is that the contents of the cell (the label and rectangle) are only shown in a row that is selected and the cell clicked on whereas they need to be visible at all times.
I have tried using a Grid inside the DataTemplate and using the Grid's FocusManager.Focused element to give the Rectangle Focus but this hasn't changed the behaviour.
<t:DataGrid x:Name="layersGrid" ItemsSource="{Binding Layers}"
SelectedItem="{Binding SelectedLayer, Mode=TwoWay}" SelectionMode="Single">
<t:DataGridTemplateColumn Visibility="Visible">
<t:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid FocusManager.FocusedElement="{Binding ElementName=swatch}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Colour.ColourProperty}"/>
<Rectangle Name="swatch" Fill="{Binding Colour, Converter={StaticResource colourConverter}}"
MouseLeftButtonDown="swatch_MouseLeftButtonDown"/>
</StackPanel>
</Grid>
</DataTemplate>
</t:DataGridTemplateColumn.CellEditingTemplate>
</t:DataGridTemplateColumn>
</t:DataGrid.Columns>
</t:DataGrid>
Additionally, once you change the colour of the layer in the model view, the rectangle hasn't updated until another row is selected and then the changed one is selected again.
private void swatch_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Colour col = LaunchColourPickerCode();
((LayersModel)this.Resources[MODEL]).SelectedLayer.Colour = col;
}
The problem with them not displaying has been fixed by using CellTemplate instead of a CellEditingTemplate
I adapted surfen's answer on this page to solve the selection problem
How to perform Single click checkbox selection in WPF DataGrid?
Replacing his method with this:
private static void GridColumnFastEdit(DataGridCell cell, RoutedEventArgs e)
{
if (cell == null || cell.IsEditing || cell.IsReadOnly)
return;
DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
if (dataGrid == null)
return;
if (!cell.IsFocused)
{
cell.Focus();
}
DataGridRow row = FindVisualParent<DataGridRow>(cell);
if (row != null && !row.IsSelected)
{
row.IsSelected = true;
}
}
and adding an event on the swatch to obtain the cell it is in
private void swatch_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DataGridCell cell = null;
while (cell == null)
{
cell = sender as DataGridCell;
if (((FrameworkElement)sender).Parent != null)
sender = ((FrameworkElement)sender).Parent;
else
sender = ((FrameworkElement)sender).TemplatedParent;
}
GridColumnFastEdit(cell, e);
}
Also thanks to kmatyaszek
I have datagrid and it's loaded event I would like to create event handler for Mouse Enter and Mouse Leave.
I was able to do it for Datagrid Column header but I dont know how to do it for Row.
Here is code:
for (int i = 0; i < grid.Columns.Count; i++)
{
DataGridColumnHeader columnHeader = DataGridHelper.GetColumnHeader(this, i);
if (columnHeader != null)
{
columnHeader.MouseEnter += new MouseEventHandler(ColumnHeader_MouseEnter);
columnHeader.MouseLeave += new MouseEventHandler(ColumnHeader_MouseLeave);
}
}
Pls Help me.
Thanks
Dee
I would use a Style
<Style TargetType="{x:Type DataGridRowHeader}">
<EventSetter Event="MouseEnter" Handler="MyMouseEnterHandler"/>
<EventSetter Event="MouseLeave" Handler="MyMouseLeaveHandler"/>
</Style>
You should be able to do the same for the column headers and get rid of the code behind.
Is there some way to set the Height attribute of a WPF multi-select ListBox to be a multiple of the item height, similar to setting the size attribute of an html select element?
I have a business requirement to not have half an item showing at the bottom of the list (if it's a long list with a scrollbar), and not have extra white space at the bottom (if it's a short list with all items showing), but the only method I can find to do this is to just keep tweaking the Height until it looks about right.
(What else have I tried? I've asked colleagues, searched MSDN and StackOverflow, done some general Googling, and looked at what VS Intellisense offered as I edited the code. There's plenty of advice out there about how to set the height to fit the ListBox's container, but that's the opposite of what I'm trying to do.)
Yeah, one could imagine there would be an easier way to do it (a single snapToWholeElement property). I couldn't find this property as well.
To achieve your requirement, I've wrote a little logic. Basically, In my Windows object I've a public property lbHeight which is calculate the listbox height by calculating the height of each individual item.
First, let's take a look at the XAML:
<Window
x:Class="SO.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="120" SizeToContent="Height"
Title="SO Sample"
>
<StackPanel>
<ListBox x:Name="x_list" Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=lbHeight}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Border x:Name="x" Background="Gray" Margin="4" Padding="3">
<TextBlock Text="{Binding}" />
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
Note that the ItemTemplate is somewhat non trivial. One important thing to notice is that I gave this item a Name - so I can find it later.
In the code-behind constructor I put some data in the list box:
public MainWindow( )
{
InitializeComponent( );
this.x_list.ItemsSource = Enumerable.Range( 0, 100 );
}
next, I'm implementing a findVisualItem - to find the root element of the data template. I've made this function a little generic, so it get a predicate p which identify whether this is the element I want to find:
private DependencyObject findVisualItem( DependencyObject el, Predicate<DependencyObject> p )
{
DependencyObject found = null;
if( p(el) ) {
found = el;
}
else {
int count = VisualTreeHelper.GetChildrenCount( el );
for( int i=0; i<count; ++i ) {
DependencyObject c = VisualTreeHelper.GetChild( el, i );
found = findVisualItem( c, p );
if( found != null )
break;
}
}
return found;
}
I'll use the following predicate, which returns true if the element I'm looking for is a border, and its name is "x". You should modify this predicate to match your root element of your ItemTemplate.
findVisualItem(
x_list,
el => { return ( el is Border ) ? ( (FrameworkElement)el ).Name == "x" : false; }
);
Finally, the lbHeight property:
public double lbHeight
{
get {
FrameworkElement item = findVisualItem(
x_list,
el => { return ( el is Border ) ? ( (FrameworkElement)el ).Name == "x" : false; }
) as FrameworkElement;
if( item != null ) {
double h = item.ActualHeight + item.Margin.Top + item.Margin.Bottom;
return h * 12;
}
else {
return 120;
}
}
}
I've also made the Window implementing INotifyPropertyChanged, and when the items of the list box were loaded (Loaded event of ListBox) I fired a PropertyChanged event for the 'lbHeight' property. At some point it was necessary, but at the end WPF fetched the lbHeight property when I already have a rendered Item.
It is possible your Items aren't identical in Height, in which case you'll have to sum all the Items in the VirtualizedStackPanel. If you have a Horizontal scroll bar, you'll have to consider it for the total height of course. But this is the overall idea. It is only 3 hours since you published your question - I hope someone will come with a simpler answer.
This is done by setting parent control Height property to Auto, without setting any size to the Listbox itself (or also setting to Auto).
To limit the list size you should also specify MaxHeight Property
iv'e got 2 itemscontrols bound to 2 observable collections
my ItemsControls :
<ItemsControl Grid.Column="4" Name="Pipe19" ItemsSource="{Binding Path=Pipes[19].Checkers}" Style="{StaticResource ItemsControlStyle}" ItemsPanel="{StaticResource TopPipePanelTemplate}" />
<ItemsControl Grid.Column="5" Name="Pipe18" ItemsSource="{Binding Path=Pipes[18].Checkers}" Style="{StaticResource ItemsControlStyle}" ItemsPanel="{StaticResource TopPipePanelTemplate}" />
their definitions through style :
<Style TargetType="{x:Type ItemsControl}" x:Key="ItemsControlStyle">
<Setter Property="ItemTemplate" Value="{StaticResource PipeDataItem}"></Setter>
<Setter Property="AllowDrop" Value="True"></Setter>
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="ItemsControl_MouseLeftButtonDown"></EventSetter>
<EventSetter Event="Drop" Handler="ItemsControl_Drop"></EventSetter>
</Style>
both are considered drop targets , they contain ellipse items which i need to drag from one to the other . the problem is the Add part of the operation doesn't get rendered to the UI .
Visual Helper :
when drag drop as ellipse from the right one to the left :
this is the code which grabs the ellipse and saves the source control
private void ItemsControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ItemsControl control = (ItemsControl)sender;
UIElement element = control.InputHitTest(e.GetPosition(control)) as UIElement;
if (element != null)
{
Ellipse ellipse = element as Ellipse;
DragSource = control;
DragDrop.DoDragDrop(ellipse, ellipse, DragDropEffects.Copy);
}
}
this is the code where the ellipse is dropped onto it's target :
private void ItemsControl_Drop(object sender, DragEventArgs e)
{
ItemsControl target = (ItemsControl)sender;
Ellipse ellipse = (Ellipse)e.Data.GetData(typeof(Ellipse));
((ObservableCollection<Checker>)DragSource.ItemsSource).Remove(ellipse.DataContext as Checker);
((ObservableCollection<Checker>)target.ItemsSource).Add(ellipse.DataContext as Checker);
}
the Checkers collections are described as follows : (Take notice that they are the itemscontrols ItemsSource :
public class Pipe
{
private ObservableCollection<Checker> checkers;
public ObservableCollection<Checker> Checkers
{
get
{
if (checkers == null)
checkers = new ObservableCollection<Checker>();
return checkers;
}
}
}
after the ItemsControl_Drop event the result is that only the remove updated the UI but the add on the target add not (i would expect that a new item would appear on the left one which i called Add on it's itemsource :
Another Visual Aid :
any ideas ?
Maybe the problem is that your adding to the ItemsSource of the ItemsControl instead of directly to the Pipes[#].Checkers.
I notice that the remove method is a remove from DragSource.ItemsSource.
You probably have a DragTargetControl which ItemsSource you should add to, which will update the Binding.
In the way your doing it you might be undoing the Binding if you cast the Binded ItemsSource to an ObservableCollection.
I think I know what is going on. I bet Pipes[] is not an ObservableCollection. Try it with Pipe1 and Pipe2. Remove from Pipe1 and add to Pipe2.
it turns out the ui element some how loses it's reference if you remove it
from one observable collection before adding it to another ,
so i added the ellipse to the target and then removed it from the source .
private void ItemsControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ItemsControl control = (ItemsControl)sender;
UIElement element = control.InputHitTest(e.GetPosition(control)) as UIElement;
if (element != null && (element is Ellipse))
{
Ellipse ellipse = element as Ellipse;
DragSource = control;
DragDrop.DoDragDrop(ellipse, ellipse, DragDropEffects.Move);
}
}
private void ItemsControl_Drop(object sender, DragEventArgs e)
{
ItemsControl target = (ItemsControl)sender;
Ellipse ellipse = (Ellipse)e.Data.GetData(typeof(Ellipse));
ObservableCollection<Checker> collection = target.ItemsSource as ObservableCollection<Checker>;
Checker checker = ellipse.DataContext as Checker;
collection.Add(checker);
((ObservableCollection<Checker>)DragSource.ItemsSource).Remove(ellipse.DataContext as Checker);
}
I'm trying to achieve the equivalent of a WinForms ListView with its View property set to View.List. Visually, the following works fine. The file names in my Listbox go from top to bottom, and then wrap to a new column.
Here's the basic XAML I'm working with:
<ListBox Name="thelist"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"
Orientation="Vertical" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
However, default arrow key navigation does not wrap. If the last item in a column is selected, pressing the down arrow does not go to the first item of the next column.
I tried handling the KeyDown event like this:
private void thelist_KeyDown( object sender, KeyEventArgs e ) {
if ( object.ReferenceEquals( sender, thelist ) ) {
if ( e.Key == Key.Down ) {
e.Handled = true;
thelist.Items.MoveCurrentToNext();
}
if ( e.Key == Key.Up ) {
e.Handled = true;
thelist.Items.MoveCurrentToPrevious();
}
}
}
This produces the last-in-column to first-in-next-column behavior that I wanted, but also produces an oddity in the left and right arrow handling. Any time it wraps from one column to the next/previous using the up/down arrows, a single subsequent use of the left or right arrow key moves the selection to the left or right of the item that was selected just before the wrap occured.
Assume the list is filled with strings "0001" through "0100" with 10 strings per column. If I use the down arrow key to go from "0010" to "0011", then press the right arrow key, selection moves to "0020", just to the right of "0010". If "0011" is selected and I use the up arrow key to move selection to "0010", then a press of the right arrow keys moves selection to "0021" (to the right of "0011", and a press of the left arrow key moves selection to "0001".
Any help achieving the desired column-wrap layout and arrow key navigation would be appreciated.
(Edits moved to my own answer, since it technically is an answer.)
It turns out that when it wraps around in my handling of the KeyDown event, selection changes to the correct item, but focus is on the old item.
Here is the updated KeyDown eventhandler. Because of Binding, the Items collection returns my actual items rather than ListBoxItems, so I have to do a call near the end to get the actual ListBoxItem I need to call Focus() on. Wrapping from last item to first and vice-versa can be achieved by swapping the calls of MoveCurrentToLast() and MoveCurrentToFirst().
private void thelist_KeyDown( object sender, KeyEventArgs e ) {
if ( object.ReferenceEquals( sender, thelist ) ) {
if ( thelist.Items.Count > 0 ) {
switch ( e.Key ) {
case Key.Down:
if ( !thelist.Items.MoveCurrentToNext() ) {
thelist.Items.MoveCurrentToLast();
}
break;
case Key.Up:
if ( !thelist.Items.MoveCurrentToPrevious() ) {
thelist.Items.MoveCurrentToFirst();
}
break;
default:
return;
}
e.Handled = true;
ListBoxItem lbi = (ListBoxItem) thelist.ItemContainerGenerator.ContainerFromItem( thelist.SelectedItem );
lbi.Focus();
}
}
}
You should be able to do it without the event listener using KeyboardNavigation.DirectionalNavigation, e.g.
<ListBox Name="thelist"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
KeyboardNavigation.DirectionalNavigation="Cycle">