Issue with Swipe inside ListView - wpf

I am using a ListView in a Windows Store App.
Whenever I start swiping(using simulator tap mode) over the list view all the items move together as illustrated in the picture.
How can I disable this manipulation event?

To your ListView, add:
ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollMode="Disabled"
If that is not enough (this sometimes does not work with MouseWheel events, in that the events still tend to be caught in the ListView and also tends to happen if the list inside of the ScrollViewer is particularly large, I've found), then you need to create a custom control to specifically ignore the event, such as this for PointerWheelChanged.
public class CustomListView : ListView
{
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
var sv = this.GetTemplateChild("ScrollViewer") as UIElement;
if (sv != null)
sv.AddHandler(UIElement.PointerWheelChangedEvent, new PointerEventHandler(OnPointerWheelChanged), true);
}
private void OnPointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
e.Handled = false;
}
}
This will disable mouse wheel scrolling inside of your ListView. You'll have to change your XAML reference to the ListView from <ListView> to <namespace:ListView> where namespace is the namespace you've created your ListView in.

Related

Disabling drag-to-scroll in ListView in WPF

I'm working in WPF to create a ListView component. The items in the list are based on another user control that reacts to MouseLeftDown events. The List also reacts to SelectionChanged events.
Right now, if I mouse down on any item on the list and move the cursor, the other items I pass along react to the SelectionChanged event (which is expected since the selection is changing as per the Mouse Down event in List view). I need to be able to disable this reaction when its down through a drag-to-scroll behavior, but to keep it active when the user selects an item on the list.
Does anyone have any ideas how this can be achieved?
Thanks everyone,
RK
I believe one of the ways that could help you is to implement your own handlers of MouseUp and MouseDown events of your items to select item on MouseUp instead of MouseDown. You could start from a sample like this:
public class MyListView : ListView
{
protected override DependencyObject GetContainerForItemOverride()
{
return new MyListViewItem();
}
}
public class MyListViewItem : ListViewItem
{
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
return;
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
}
}

WPF expand TreeView on single mouse click

I have a WPF TreeView with a HierarchicalDataTemplate.
Currently I have to double click an item to expand/collapse it.
I would like to change this behaviour to a single click, without loosing other functionality. So it should expand and collapse on click.
What is the recommended way to do this?
Thanks!
You could use a re-templated checkbox as your node (containing whatever template you are currently using) with its IsChecked property bound to the IsExpanded property of the TreeViewItem.
Here is a template I've just test that seems to do the job:
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}, Path=IsExpanded}">
<CheckBox.Template>
<ControlTemplate>
<TextBlock Text="{Binding Header}"></TextBlock>
</ControlTemplate>
</CheckBox.Template>
</CheckBox>
</HierarchicalDataTemplate>
Just replace the ControlTemplate contents with whatever you need.
If you are using a standard TreeViewItem, then you can capture the click event:
private void OnTreeViewMouseUp( object sender, MouseButtonEventArgs e )
{
var tv = sender as TreeView;
var item = tv.SelectedItem as TreeViewItem;
if( item != null )
item.IsExpanded = !item.IsExpanded;
e.Handled = true;
}
private void OnTreeViewPreviewMouseDoubleClick( object sender, MouseButtonEventArgs e )
{
e.Handled = true;
}
Most likely in your case, you'll need to do something with your binding and ViewModel. Here's a good article from CodePlex: Simplifying the WPF TreeView by Using the ViewModel Pattern.
Just use selected item changed event and use the following,
private void treeview_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
TreeViewItem item = (TreeViewItem)treeview.SelectedItem;
item.IsExpanded = true;
}
where treeview is the name of your TreeView, you could include an if to close/open based on its current state.
I have very little experience working with WPF to this point, so I am not 100% certain here. However, you might check out the .HitTest method of both the Treeview and TreeView Item (the WPF Treeview is essentially the Windows.Controls.Treeview, yes? Or a derivation thereof?).
THe HIt Test method does not always automatically appear in the Intellisense menu for a standard Windows.Forms.Treeview (I am using VS 2008) until you type most of the method name. But it should be there. You may have to experimnt.
You can use the .HitTest Method to handle the MouseDown event and return a reference to the selected treeview item. You must test for a null return, however, in case the use clicks in an area of the control which contains no Tree Items. Once you have a reference to a specific item, you should be able to set its .expanded property to the inverse of whatever it is currently. again, some experimentation may be necessary here.
As I said, I have not actually used WPF yet, so I could have this Wrong . . .
The answer of Metro Smurf (thanks to which I got where I wanted to be) suggests the right approach . You could simply hook up to the SelectedItemChanged event of the Treeview. Then cast the e.NewValue passed in the eventhandler as TreeViewItem, and access its IsExpanded property to set it to true.
void MyFavoritesTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
((TreeViewItem)e.NewValue).IsExpanded = true;
}
Then for the final touch, you can also hook up the items in your Treeview by casting them as TreeViewItem as suggested, and then you can hook up to the various manipulation events, like:
var item = tv.SelectedItem as TreeViewItem;
item.Expanded += item_Expanded;
And then do whatever you need to do in the eventhandler
void item_Expanded(object sender, RoutedEventArgs e)
{
// handle your stuff
}

How to correctly resize scrollbar when underlying collection of a WPF ListView changes?

How to correctly resize scrollbar when underlying collection of a WPF ListView changes?
I have a WPF ListView bound to an observeable collection with several thousand items. When a large number of these are removed the view seems to only show the last item. When I move the position in the view with the thumbbar, the thumbbar resizes to reflect the new collection size. Is it possible to force the ListView and Scroll bar to synchronise when the collection changes?
I have found a work-around if anyone else has this problem.
The following code example shows the items source of the ListView bening changed on the first line. The following lines show the workaround which is just to scroll back to the first item.
this.ListViewResults.ItemsSource = this.itemsFiltered;
object firstItem = this.ListViewResults.Items.GetItemAt(0);
if(firstItem == null)
{
return;
}
this.ListViewResults.ScrollIntoView(firstItem);
Strange behaviour!!
I would try setting the binding context (Context) of the ListView to null, and then the same list again in order to refresh the bindings.
I have a different workaround which requires subclassing ListView. It's a bit more work but the result is better than just scrolling to the first item. But you'll need to adapt the ListView template such that the ScrollViewer in the template has a name (here PART_ScrollViewer) or you use another way to get the ScrollViewer object.
public class BetterListView : ListView
{
ScrollViewer sv;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//Get the scrollviewer in the template (I adapted the ListView template such that the ScrollViewer has a name property)
sv = (this.Template.FindName("PART_ScrollViewer", this)) as ScrollViewer;
}
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
//Prevent the bug where the ListView doesn't scroll correctly when a lot of items are removed
if (sv != null && e.Action == NotifyCollectionChangedAction.Remove)
{
sv.InvalidateScrollInfo();
}
}
}

Making a WPF TabControl ignore Ctrl+Tab

I'm writing an app that uses the "tabbed browsing" metaphor, with a TabControl the full size of the window, and other stuff inside the tabs. Sometimes those tabs will themselves contain other TabControls.
(Tabs inside tabs can be confusing, so I'll re-style the inner TabControl so it doesn't look like a TabControl. I'll probably style it to use ToggleButtons at the top instead of tabs.)
I want this UI to behave like you would expect the tabbed-browsing metaphor to work: Ctrl+Tab should always switch tabs on the outer TabControl (the one that looks like a TabControl), even if keyboard focus is inside the inner TabControl (which doesn't look like a TabControl, and therefore shouldn't be expected to respond to Ctrl+Tab). But, of course, the inner TabControl gets the key event first and handles it itself.
What's the best way to keep the inner TabControl from responding to the Ctrl+Tab and Ctrl+Shift+Tab key events, so those events can bubble up to the outer TabControl?
The WPF TabControl appears to manage the keyboard navigation feature via the OnKeyDown method. I would suggest creating a custom control that inherits from the TabControl, and override the OnKeyDown method.
You can handle the PreviewKeyDown event on your inner TabControl and set e.Handled = true to prevent it from handling key events. You can then find the parent TabControl (perhaps recursively through ((TabControl)sender).Parent ) and change its SelectedIndex programmatically.
Wrapping that up in a custom control would keep it reasonably clean.
As an alternative to creating a Custom Control as suggested here, you could create an "Attached behaviour" to encapsulate this:
namespace WpfApplication1
{
using System.Windows;
using System.Windows.Input;
public static class IgnoreCtrlTabBehaviour
{
//Setter for use in XAML: this "enables" this behaviour
public static void SetEnabled(DependencyObject depObj, bool value)
{
depObj.SetValue(EnabledProperty, value);
}
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.RegisterAttached("Enabled", typeof(bool),
typeof(IgnoreCtrlTabBehaviour),
new FrameworkPropertyMetadata(false, OnEnabledSet));
static void OnEnabledSet(DependencyObject depObj, DependencyPropertyChangedEventArgs args)
{
var uiElement = depObj as UIElement;
uiElement.PreviewKeyDown +=
(object _, System.Windows.Input.KeyEventArgs e) =>
{
if (e.Key == Key.Tab &&
(Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
e.Handled = true;
}
};
}
}
}
Use in XAML like this:
<Window x:Class="WpfApplication1.MainWindow"
...
xmlns:local="clr-namespace:WpfApplication1"
...
<TabControl local:IgnoreCtrlTabBehaviour.Enabled="True">
<TabItem Header="tab1">
...

Synchronizing scroll positions for 2 WPF DataGrids

I am trying to synchronize the horizontal scroll position of 2 WPF DataGrid controls.
I am subscribing to the ScrollChanged event of the first DataGrid:
<toolkit:DataGrid x:Name="SourceGrid" ScrollViewer.ScrollChanged="SourceGrid_ScrollChanged">
I have a second DataGrid:
<toolkit:DataGrid x:Name="TargetGrid">
In the event handler I was attempting to use the IScrollInfo.SetHorizontalOffset, but alas, DataGrid doesn't expose IScrollInfo:
private void SourceGrid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
((IScrollInfo)TargetGrid).SetHorizontalOffset(e.HorizontalOffset);
// cast to IScrollInfo fails
}
Is there another way to accomplish this? Or is there another element on TargetGrid that exposes the necessary IScrollInfo to achieve the synchronization of the scroll positions?
BTW, I am using frozen columns, so I cannot wrap both DataGrid controls with ScrollViewers.
There is great piece of code to do this:
http://www.codeproject.com/KB/WPF/ScrollSynchronization.aspx
According to the Microsoft product group, traversing the visual tree to find the ScrollViewer is the recommended method, as explained in their answer on Codeplex.
We had this same problem when using the Infragistics grid because it didn't (still doesn't) support frozen columns. So we had two grids side-by-side that were made to look as one. The grid on the left didn't scroll horizontally but the grid on the right did. Poor man's frozen columns.
Anyway, we ended up just reaching into the visual tree and pulling out the ScrollViewer ourselves. Afterall, we knew it was there - it just wasn't exposed by the object model. You could use a similar approach if the WPF grid does not expose the ScrollViewer. Or you could subclass the grid and add the functionality you require to make this work.
Interested in hearing why you need to do this.
This is a great solution. Worked fine for me in WPF.
http://www.codeproject.com/Articles/39244/Scroll-Synchronization
I just made a reference to ScrollSynchronizer dll, added a xml import:
xmlns:scroll="clr-namespace:ScrollSynchronizer"
then just added this to both my datagrids and bobs your uncle:
<DataGrid.Resources>
<Style TargetType="ScrollViewer">
<Setter Property="scroll:ScrollSynchronizer.ScrollGroup" Value="Group1" />
</Style>
</DataGrid.Resources>
You can trick the datagrid to expose its ScrollViewer as public property for each grid, when for example innerGridControl_ScrollChanged() handler called during initialisation of the usercontrol.
To expose it you can make your grid in an xaml View file, and then compose two of them in another xaml View.
Below code is on the innerGrid.xaml.cs for example:
public ScrollViewer Scroller { get; set; } // exposed ScrollViewer from the grid
private bool _isFirstTimeLoaded = true;
private void innerGridControl_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (_isFirstTimeLoaded) // just to save the code from casting and assignment after 1st time loaded
{
var scroller = (e.OriginalSource) as ScrollViewer;
Scroller = scroller;
_isFirstTimeLoaded = false;
}
}
on OuterGridView.xaml put an attached event handler definition:
<Views:innerGridView Grid.Row="1" Margin="2,0,2,2" DataContext="{Binding someCollection}"
x:Name="grid1Control"
ScrollViewer.ScrollChanged="Grid1Attached_ScrollChanged"
></Views:innerGridView>
<Views:innerGridView Grid.Row="3" Margin="2,0,2,2" DataContext="{Binding someCollection}"
x:Name="grid2Control"
ScrollViewer.ScrollChanged="Grid2Attached_ScrollChanged"
></Views:innerGridView>
then access that public ScrollViewer.SetHorizontalOffset(e.HorizontalOffset) method when another scrolling event occur.
Below code is in the OuterGridView.xaml.cs on one of the handler definition (
private void Grid1Attached_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (e != null && !e.Handled)
{
if (e.HorizontalChange != 0.0)
{
grid2Control.Scroller.ScrollToHorizontalOffset(e.HorizontalOffset);
}
e.Handled = true;
}
}
private void Grid2Attached_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (e != null && !e.Handled)
{
if (e.HorizontalChange != 0.0)
{
grid1Control.Scroller.ScrollToHorizontalOffset(e.HorizontalOffset);
}
e.Handled = true;
}
}
Also make sure any other scroll_changed event inside the inner grid (if any, for example if you define a TextBox with default scroller in one of the column data template) has its e.Handled set to true to prevent outer grid's handler processing it (this happened due to default bubbling behaviour of routedevents). Alternatively you can put additional if check on e.OriginalSource or e.Source to filter the scroll event you're intended to process.

Resources