ScrollViewer ChangeView on SizeChanged - c#

I have DataTemplate which I use in HubSection:
<DataTemplate x:Name="dataTemplate2">
<Grid x:Name="greedy">
<ScrollViewer x:Name="scroller" SizeChanged="ScrollViewer_SizeChanged" Height="{Binding Height,ElementName=greedy}" >
<ItemsControl x:Name="itemsControl"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource dataTemplateDetails}">
<ItemsControl.ItemContainerTransitions>
<TransitionCollection>
<ReorderThemeTransition />
<NavigationThemeTransition />
</TransitionCollection>
</ItemsControl.ItemContainerTransitions>
</ItemsControl>
</ScrollViewer>
</Grid>
</DataTemplate>
In my ItemsControl I have items which can be expandable. What I want to achieve is that when the item will expand to see more details about that item. I want Scrollviewer to scroll down (for amount of changed size of ScrollViewer).
Code behind for SizeChanged event:
private void ScrollViewer_SizeChanged(object sender, SizeChangedEventArgs e)
{
ScrollViewer myScroll = (ScrollViewer)sender;
myScroll.ChangeView(null, myScroll.ScrollableHeight, null, false);
}
What I have done now is not working as my expectation. I scroll down to the end right now. But the thing is it scrolls only when the items extend the size of available view (ScrollBar is shown). Then if I expand another item it doesn't work. If I hide details about item (ScrollBar hides too) and expand it again it will work again.
It's like the SizeChanged event occures only when ScrollViewer is going into action but have infinite height which doesn't change.
I've tried Grid with row set to "*", it changes nothing. Now I try to set height by binding it to height of ItemsControl - still the same behaviour.
Could you help me with the solution, show the path of thinking or enlighten me with some workaround?
EDIT:
I prepared some code to work with to see what happens exactly.
1) Create New Project -> Store Apps (c#) -> Windows Phone 8.1 (Blank App) and name it "scroll"
2) Paste this code into MainPage.xaml
<Page
x:Class="scroll.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:scroll"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="DarkOliveGreen">
<Page.Resources>
<DataTemplate x:Name="dataTemplateDetails">
<Grid Name="grido" Grid.Row="1" Margin="10,10,10,10">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="Auto" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="Black" CornerRadius="10" Opacity="0.4" />
<Border Grid.Row="1" Background="Black" CornerRadius="10" Opacity="0.3" />
<Border Grid.Row="2" Background="Black" CornerRadius="10" Opacity="0.2" />
<Border Grid.Row="3" Background="Black" CornerRadius="10" Opacity="0.1" />
<TextBlock Grid.Row="0" Text="{Binding Name}" Style="{StaticResource BaseTextBlockStyle}" HorizontalAlignment="Center"/>
<Grid Grid.Row="1" HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Margin="5,0,5,0" Text="Description:" Style="{StaticResource BaseTextBlockStyle}"/>
<TextBlock Grid.Column="1" Text="{Binding Description}" Style="{StaticResource BaseTextBlockStyle}"/>
</Grid>
<TextBlock Grid.Row="2" Text="Next row" Style="{StaticResource BaseTextBlockStyle}" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="3" Text="Next row" Style="{StaticResource BaseTextBlockStyle}" HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
<DataTemplate x:Name="dataTemplate2">
<Grid x:Name="greedy" >
<ScrollViewer x:Name="scroller" SizeChanged="ScrollViewer_SizeChanged" VerticalAlignment="Top">
<ItemsControl x:Name="itemsControl"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource dataTemplateDetails}">
<ItemsControl.ItemContainerTransitions>
<TransitionCollection>
<ReorderThemeTransition />
<NavigationThemeTransition />
</TransitionCollection>
</ItemsControl.ItemContainerTransitions>
</ItemsControl>
</ScrollViewer>
</Grid>
</DataTemplate>
</Page.Resources>
<Page.BottomAppBar>
<CommandBar Background="Black" Opacity="0.6" x:Name="myCommandBar">
<AppBarButton Icon="Add" Label="Add" x:Name="AddItem" Click="Add_Click"/>
<AppBarButton Icon="Delete" Label="Delete" x:Name="RemoveItem" Click="Delete_Click"/>
</CommandBar>
</Page.BottomAppBar>
<Grid>
<Hub x:Name="myHub" Header="Test">
<HubSection x:Uid="myDetailsHubsection" x:Name="myDetailsHubsection" Header="Details" DataContext="{Binding Items}" ContentTemplate="{StaticResource dataTemplate2}" />
</Hub>
</Grid>
3) Paste this code into MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace scroll
{
public sealed partial class MainPage : Page
{
public static Details dataContextItems;
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
dataContextItems = new Details();
}
public class TestItem
{
public string Name { get; set; }
public string Description { get; set; }
public TestItem(string n, string d)
{
Name = n;
Description = d;
}
}
public class Details : INotifyPropertyChanged
{
private ObservableCollection<TestItem> _items;
public ObservableCollection<TestItem> Items
{
set
{
_items = value;
NotifyPropertyChanged("Items");
}
get
{
return _items;
}
}
public Details()
{
_items = new ObservableCollection<TestItem>();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.DataContext = dataContextItems;
}
private void Add_Click(object sender, RoutedEventArgs e)
{
TestItem iAmAnItem = new TestItem("Name of an item", "Long and detailed description of an item");
dataContextItems.Items.Add(iAmAnItem);
}
private void Delete_Click(object sender, RoutedEventArgs e)
{
if (dataContextItems.Items.Count > 0)
dataContextItems.Items.RemoveAt(0);
}
private void ScrollViewer_SizeChanged(object sender, SizeChangedEventArgs e)
{
ScrollViewer myScroll = (ScrollViewer)sender;
myScroll.ChangeView(null, myScroll.ScrollableHeight, null, false);
}
}
}
4) Run the app
5) When you add first two items you can't scroll them but when you add more items you can see that it scrolls down as soon as items "need more space" to be shown and scrollbar occures. But with adding more items it doesn't work. If you delete items and add "third" item again it will scroll down.
I want it to scroll down everytime size of scrollviewer changes (in this case when new item occures but keep in mind it should work when item "extends" in my original solution and there can be few extended items simultaneously).

I've been tinkering with the solution and I finally found a way to do that.
I think the problem is that I didn't understand how ScrollViewer does work. I took the scrolling height as UIElement height hoping for SizeChanged to be fired what isn't truth. ScrollViewer wasn't changing its size because it just took the whole space it could and then just displayed how much content it is in it (It's like ScrollViewer has almost always infinite height unless it's less than actual available view space). With adding first two items SizeChanged event was firing with third one too and then nothing happend. It proves that.
I needed SizeChanged to be fired everytime the size of ScrollViewer (or in this case the Grid) was changing. Solution is very simple but still it needs understanding of how ScrollViewer works - and now it seems so obvious that it will never be greater than available space.
Changes made to make it work:
<DataTemplate x:Name="dataTemplate2">
<ScrollViewer x:Name="scroller" VerticalAlignment="Top" HorizontalAlignment="Stretch" IsEnabled="True" >
<Grid VerticalAlignment="Top" x:Name="greedo" SizeChanged="greedo_SizeChanged">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ItemsControl x:Name="itemsControl"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource dataTemplateDetails}">
<ItemsControl.ItemContainerTransitions>
<TransitionCollection>
<ReorderThemeTransition />
<NavigationThemeTransition />
</TransitionCollection>
</ItemsControl.ItemContainerTransitions>
</ItemsControl>
</Grid>
</ScrollViewer>
</DataTemplate>
and code behind:
private void greedo_SizeChanged(object sender, SizeChangedEventArgs e)
{
Grid takingScroll = (Grid)sender;
ScrollViewer myScroll = (ScrollViewer)takingScroll.Parent;
myScroll.ChangeView(null, myScroll.ScrollableHeight, null, false);
}

Related

ListBox Selected Items are reset each time I switch tabs in may TabControl

I have the following TabControl:
<TabControl Name="tabControl" Grid.Row="0" MinWidth="270" HorizontalAlignment="Stretch" ItemsSource="{Binding Counters}" ContentTemplate="{StaticResource templateForTheContent}"
ItemTemplate="{StaticResource templateForTheHeader}">
</TabControl>
It uses this DataTemplate:
<Window.Resources>
<DataTemplate x:Key="templateForTheContent" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0"
ItemsSource="{Binding}"
SelectionMode="Multiple"
BorderThickness="1" BorderBrush="#FF8B8B8B" SelectionChanged="ListBox_SelectionChanged_1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding CounterName, Mode=OneWay}" />
<Run Text="{Binding InstanceName, Mode=OneWay}" />
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Name="RAMSelectAllButton" Margin="0,10,0,0" Grid.Column="0" Grid.Row="1">
<TextBlock Text="SELECT ALL"/>
</Button>
<Button Name="RAMUnSelectAllButton" Margin="0,10,0,0" Grid.Column="1" Grid.Row="1">
<TextBlock Text="UNSELECT ALL"/>
</Button>
</Grid>
</DataTemplate>
<DataTemplate x:Key="templateForTheHeader" >
<TextBlock Text="{Binding CategoryName}"/>
</DataTemplate>
</Window.Resources>
It works as expected, binding works well, everything would be totally fine if this issue wasn't present:
Each time I switch a tab in my TabControl, selected items of a ListBox in my previous tab are reset - so when I go back to that tab - nothing is selected.
How to fix this?
//EDIT
here's my ListBox_SelectionChanged_1 method:
private void ListBox_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
System.Windows.Controls.ListBox listBoxTemp = sender as System.Windows.Controls.ListBox;
PerformanceCounter counterTemp = (PerformanceCounter)listBoxTemp.Items[0];
if (!appData.SelectedCounters.ContainsKey(counterTemp.CategoryName))
appData.SelectedCounters.Add(counterTemp.CategoryName, new List<PerformanceCounter>());
appData.SelectedCounters[counterTemp.CategoryName].Clear();
foreach (PerformanceCounter counter in listBoxTemp.SelectedItems)
{
appData.SelectedCounters[counterTemp.CategoryName].Add(counter);
}
}
ListBox is bound to Counters, which is Observable Collection:
public ObservableCollection<ObservableCollection<PerformanceCounter>> Counters
{
get { return _Counters; }
}
ObservableCollection<ObservableCollection<PerformanceCounter>> _Counters = new ObservableCollection<ObservableCollection<PerformanceCounter>>();
(i am not familiar with TabControls, or WPF for that matter, but i would suggest a solution like this:)
Backup the selected items of your ListBox in a Dictionary.
var selectionBackups = new Dictionary<ListBox, IEnumerable<ListBoxItem>>();
I'd keep this field updated in ListBox_SelectionChanged_1(). Whenever you enter a Tab, overwrite the concerned ListBox's selection.
void TabEnter(object sender, TabEventArgs e)
{
ListBox lb = ... //acquire the current Listbox
OverwriteSelection(lb, selectionBackups[lb]); //set the ListBox's selection
}
(You might want to prevent the ListBox_SelectionChanged_1() from triggering when you restore the selection. That could be done like this:
bool auto_select = false; //set this to true while editing selections
private void ListBox_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
if(auto_select)
return;
...
}
)

ContinuumNavigationTransitionInfo ExitElement not animating in Windows 10 UWP project

I'm attempting to use the ContinuumNavigationTransition effect in a Windows 10 UWP app. I can get the EntranceElement to correctly fly in when navigating pages, but the ExitElement never animates out.
As a repro case, I made a bare minimum app, with still no success. My code looks like the following:
MainPage.xaml
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<ListView x:Name="TheList"
ContinuumNavigationTransitionInfo.ExitElementContainer="True"
ItemsSource="{x:Bind SomeItems}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
Style="{StaticResource HeaderTextBlockStyle}"
ContinuumNavigationTransitionInfo.IsExitElement="True"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Navigate"
HorizontalAlignment="Center"
Tapped="Button_Tapped"/>
</StackPanel>
</Grid>
MainPage.xaml.cs
public ObservableCollection<String> SomeItems = new ObservableCollection<string>
{
"Item 1!",
"Item 2!"
};
public MainPage()
{
this.InitializeComponent();
}
private void Button_Tapped(object sender, TappedRoutedEventArgs e)
{
((Frame)Window.Current.Content).Navigate(typeof(SubPage));
}
SubPage.xaml
<Page.Transitions>
<TransitionCollection>
<NavigationThemeTransition>
<NavigationThemeTransition.DefaultNavigationTransitionInfo>
<ContinuumNavigationTransitionInfo/>
</NavigationThemeTransition.DefaultNavigationTransitionInfo>
</NavigationThemeTransition>
</TransitionCollection>
</Page.Transitions>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="I'm an entrance element!"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContinuumNavigationTransitionInfo.IsEntranceElement="True"
TextWrapping="WrapWholeWords"
Style="{StaticResource SubheaderTextBlockStyle}"/>
</Grid>
SubPage.xaml.cs
public SubPage()
{
this.InitializeComponent();
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
SystemNavigationManager.GetForCurrentView().BackRequested += SubPage_BackRequested;
}
private void SubPage_BackRequested(object sender, BackRequestedEventArgs e)
{
((Frame)Window.Current.Content).Navigate(typeof(MainPage));
SystemNavigationManager.GetForCurrentView().BackRequested -= SubPage_BackRequested;
}
I believe where you are going wrong is that you are using a button to navigate whereas Entrance elements and exit elements are used to kind of carry the context forward. So ideally you should navigate on listview item click.Code wise I couldn't see anything wrong and check this link it might be of some use.
http://www.visuallylocated.com/post/2014/06/24/Page-transitions-and-animations-in-Windows-Phone-Runtime.aspx

How do I make the Expander Header change along with the List item as typing in field occurs?

How do I make the Expander Header change along with the ListBox item as typing in field occurs?
* I just attempted to post this with an image, but this site does not allow me to post an image until I have a reputation greater than 10. The image is simply a window capture of the running WPF program (source below) where the [New] button was clicked once, "John Henry Doe" entered into the Name field and "123-4567" entered into the Phone field, then in bold red "1) As I type here" with arrows pointing to the Name and Phone fields, then in bold red "2) This changes" with arrow pointing to the "John Henry Doe: 123-4567" item in the ListBox, then in bold red "3) But this does not change" with an arrow pointing to "New Contact" of the Expander's Header. *
As can (if I was allowed to post the image) be seen in the image above, as the user types into the Name or Phone field, the ListBox Item changes. It changes because I do a .Refresh() on the KeyUp event. However, the Header of the Expander should be changing at the same time. There is no .Refresh() for that as far as I know. I want the Expander's Header to update as the ListBox's Item does, that is, while the user is typing.
The ListBox's DataContext is an observable collection of the class Contact. The Contact class has a property called ListString with a get that returns the result of the method ListItem(). The ListBox's ItemTemplate simply binds to the ListString property. The Expander's Header is bound to ListBox's SelectedItem.ListString and currently is only updated when selecting different ListBox items. I need it to update as typing occurs.
Below is the XAML and C# code behind code. The controls to the right of the ListBox are invisible until an entry is selected in the ListBox. The [New] button inserts a new item into the ListBox and selects it, thereby casing the controls to the right to appear and focus given to the Name field. As you type into the Name field and/or the Phone field, the corresponding item in the ListBox will update, but not the Expander's Header. That does not update until you select another item in the ListBox. I want it to update at the same time the ListBox item updates. How do I do that?
<Window x:Class="Binding_List_Expander_04.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Binding List Expander 04"
Height="350"
Width="530">
<Window.Resources>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal" Margin="3">
<StackPanel Orientation="Vertical" Margin="3">
<ListBox Name="ContactList"
ItemsSource="{Binding}"
Width="166"
Height="270"
Margin="0,0,0,3">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ListString}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Name="NewItem"
Content="New"
Click="Event_NewContact_Click"
Height="23"
Width="75" />
</StackPanel>
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style TargetType="ScrollViewer">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ContactList, Path=SelectedIndex}" Value="-1">
<Setter Property="Opacity" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<ScrollViewer Height="302">
<StackPanel Orientation="Vertical">
<Expander Name="ContactExpander">
<Expander.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding ElementName=ContactList, Path=SelectedItem.ListString}" />
</DataTemplate>
</Expander.HeaderTemplate>
<StackPanel Margin="21,0,0,0"
Orientation="Vertical">
<Grid Margin="3"
TextBoxBase.TextChanged="Event_ContactName_TextChanged">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="3" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="3" />
<ColumnDefinition Width="250" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="Name:" />
<TextBox Grid.Row="0"
Grid.Column="2"
Name="ContactName"
Text="{Binding ElementName=ContactList, Path=SelectedItem.Name, Mode=TwoWay}" />
<TextBlock Grid.Row="2"
Grid.Column="0"
Text="Phone:" />
<TextBox Grid.Row="2"
Grid.Column="2"
Name="ContactPhone"
Text="{Binding ElementName=ContactList, Path=SelectedItem.Phone, Mode=TwoWay}" />
</Grid>
</StackPanel>
</Expander>
<Expander Header="
This is a place holder, there will be
many Expanders following this one."
Margin="0,10,0,0">
<StackPanel>
<TextBlock Text="Data and
Information" FontSize="30" TextAlignment="Center" />
</StackPanel>
</Expander>
<Expander Header="This is another place holder."
Margin="0,10,0,0">
<StackPanel>
<TextBlock Text="Data and
Information" FontSize="30" TextAlignment="Center" />
</StackPanel>
</Expander>
<Expander Header="This is another place holder."
Margin="0,10,0,0">
<StackPanel>
<TextBlock Text="Data and
Information" FontSize="30" TextAlignment="Center" />
</StackPanel>
</Expander>
</StackPanel>
</ScrollViewer>
</StackPanel>
</StackPanel>
</Grid>
</Window>
using System.Windows;
using System.Collections.ObjectModel;
using System.Windows.Threading;
using System.Threading;
using System.Windows.Controls;
namespace Binding_List_Expander_04
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ObservableCollection<Contact> Contacts = new ObservableCollection<Contact>();
public MainWindow()
{
InitializeComponent();
ContactList.DataContext = Contacts;
}
private void Event_NewContact_Click(object sender, RoutedEventArgs e)
{
Contacts.Insert(0, new Contact());
ContactList.SelectedIndex = 0;
if (ContactExpander.IsExpanded)
SetFocus(ContactName);
else
{
ContactExpander.IsExpanded = true;
SetFocus(ContactName);
}
}
public void SetFocus(UIElement control)
{
control.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, (ThreadStart)delegate { control.Focus(); });
}
private void Event_ContactName_TextChanged(object sender, TextChangedEventArgs e)
{
var tb = e.Source as TextBox;
Contact C = ContactList.SelectedItem as Contact;
if (tb == ContactName)
C.Name = tb.Text;
else if (tb == ContactPhone)
C.Phone = tb.Text;
ContactList.Items.Refresh();
}
}
public class Contact
{
public string Name { get; set; }
public string Phone { get; set; }
public string ListString { get { return ListItem(); } } // See comments in ListItem() below.
public Contact()
{
Name = string.Empty;
Phone = string.Empty;
}
private string ListItem()
{/*
* This is a simplified version, the actual version is complicated and cannot be templatized.
* Please, do not suggest templitazing this. I know this simple version can be templitazed,
* but the actual version cannot be templatized. I need to know how to make this work as it
* currently is.
*/
if ((Name + Phone).Trim().Length == 0)
return "<New Contact>";
else
{
string li = Name.Trim();
if (li.Length != 0 && Phone.Trim().Length != 0) li += ": ";
return li + Phone.Trim();
}
}
}
}
Thanks for you help.

Access the value of button and checkbox of an XAML in other another class in a wpf c# application

I'm working on a WPF Kinect project. It's one of the developer toolkit samples of Windows Kinect and it's called "Kinect Explorer". You can download it from the Kinect Developer Toolkit SDK ver 1.5. In kinectwindow.xaml I added a button and a checkbox. Also, there is a class called kinectskeleton.cs in which I created two DataTables and a boolean variables. The first DataTable is filled in the OnRender function while the other is empty. The boolean variable is set by default to false. So, what I want is when the button in the kinectwindow.xaml.cs is pressed the latest data in the filled DataTable is copied to the empty one. Then, when the checkbox is checked, the boolean value is set to true. So, how to do this?
I defined a function in the class kinectskeleton.cs that copies the data from the filled to the empty DataTable. In the OnClick function of the button of kinectwindow.xaml.cs, I created an object from class kinectskeleton and called this function but both DataTables are empty. Same for the checkbox in the CheckBox_Checked function: I set the boolean value of the class kinectskelton to true (and in the unchecked function I set it to false). But, the result is that in the kinectskelton class it's always set to the default value (false) and never enters the if condition I made for it to enter when it's true.
Hope it's now more clear and waiting for any suggestions. To download the toolkit, here is the link: http://www.microsoft.com/en-us/kinectforwindows/develop/developer-downloads.aspx
part of my code:
//------------------------------------------------------------------------------
// <copyright file="KinectWindow.xaml.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace Microsoft.Samples.Kinect.KinectExplorer
{
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using Microsoft.Kinect;
using Microsoft.Samples.Kinect.WpfViewers;
/// <summary>
/// Interaction logic for KinectWindow.xaml.
/// </summary>
public partial class KinectWindow : Window
{
public static readonly DependencyProperty KinectSensorProperty =
DependencyProperty.Register(
"KinectSensor",
typeof(KinectSensor),
typeof(KinectWindow),
new PropertyMetadata(null));
private readonly KinectWindowViewModel viewModel;
/// <summary>
/// Initializes a new instance of the KinectWindow class, which provides access to many KinectSensor settings
/// and output visualization.
/// </summary>
public KinectWindow()
{
this.viewModel = new KinectWindowViewModel();
// The KinectSensorManager class is a wrapper for a KinectSensor that adds
// state logic and property change/binding/etc support, and is the data model
// for KinectDiagnosticViewer.
this.viewModel.KinectSensorManager = new KinectSensorManager();
Binding sensorBinding = new Binding("KinectSensor");
sensorBinding.Source = this;
BindingOperations.SetBinding(this.viewModel.KinectSensorManager, KinectSensorManager.KinectSensorProperty, sensorBinding);
// Attempt to turn on Skeleton Tracking for each Kinect Sensor
this.viewModel.KinectSensorManager.SkeletonStreamEnabled = true;
this.DataContext = this.viewModel;
InitializeComponent();
}
public KinectSensor KinectSensor
{
get { return (KinectSensor)GetValue(KinectSensorProperty); }
set { SetValue(KinectSensorProperty, value); }
}
public void StatusChanged(KinectStatus status)
{
this.viewModel.KinectSensorManager.KinectSensorStatus = status;
}
private void Swap_Executed(object sender, ExecutedRoutedEventArgs e)
{
Grid colorFrom = null;
Grid depthFrom = null;
if (this.MainViewerHost.Children.Contains(this.ColorVis))
{
colorFrom = this.MainViewerHost;
depthFrom = this.SideViewerHost;
}
else
{
colorFrom = this.SideViewerHost;
depthFrom = this.MainViewerHost;
}
colorFrom.Children.Remove(this.ColorVis);
depthFrom.Children.Remove(this.DepthVis);
colorFrom.Children.Insert(0, this.DepthVis);
depthFrom.Children.Insert(0, this.ColorVis);
}
public KinectSkeleton ks = new KinectSkeleton();
private void Calibrate_Click(object sender, RoutedEventArgs e)
{
ks.setcurrentdt();
}
private void AngleDifference_Checked(object sender, RoutedEventArgs e)
{
ks.is_checked = true;
}
private void AngleDifference_Unchecked(object sender, RoutedEventArgs e)
{
ks.is_checked = false;
}
}
}
/// <summary>
/// A ViewModel for a KinectWindow.
/// </summary>
public class KinectWindowViewModel : DependencyObject
{
public static readonly DependencyProperty KinectSensorManagerProperty =
DependencyProperty.Register(
"KinectSensorManager",
typeof(KinectSensorManager),
typeof(KinectWindowViewModel),
new PropertyMetadata(null));
public KinectSensorManager KinectSensorManager
{
get { return (KinectSensorManager)GetValue(KinectSensorManagerProperty); }
set { SetValue(KinectSensorManagerProperty, value); }
}
}
}
namespace Microsoft.Samples.Kinect.WpfViewers
{
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.Kinect;
using System.Data;
using System.Windows.Media.Media3D;
using System.Globalization;
/// <summary>
/// This control is used to render a player's skeleton.
/// If the ClipToBounds is set to "false", it will be allowed to overdraw
/// it's bounds.
/// </summary>
public class KinectSkeleton : Control
{
public bool is_checked=false;
public DataTable x = new DataTable();
public DataTable y = new DataTable();
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
var currentSkeleton = this.Skeleton;
// Don't render if we don't have a skeleton, or it isn't tracked
if (drawingContext == null || currentSkeleton == null || currentSkeleton.TrackingState == SkeletonTrackingState.NotTracked)
{
return;
}
// Displays a gradient near the edge of the display where the skeleton is leaving the screen
this.RenderClippedEdges(drawingContext);
switch (currentSkeleton.TrackingState)
{
case SkeletonTrackingState.PositionOnly:
if (this.ShowCenter)
{
drawingContext.DrawEllipse(
this.centerPointBrush,
null,
this.Center,
BodyCenterThickness * this.ScaleFactor,
BodyCenterThickness * this.ScaleFactor);
}
break;
case SkeletonTrackingState.Tracked:
// here i defined the DataTables
if (is_checked == false)
{
//fill data table x with value a
}
else
{
//fill data table x with value b
}
break;
}
}
public void setcurrentdt()
{
//fill empty datatable "y" with filled one "x"
y = x.Copy();
}
}
}
xaml code of the checkbox:
<Window x:Class="Microsoft.Samples.Kinect.KinectExplorer.KinectWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Microsoft.Samples.Kinect.KinectExplorer"
xmlns:kt="clr-namespace:Microsoft.Samples.Kinect.WpfViewers;assembly=Microsoft.Samples.Kinect.WpfViewers"
Title="Kinect Explorer" Width="839" Height="768">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Microsoft.Samples.Kinect.WpfViewers;component/KinectControlResources.xaml"/>
</ResourceDictionary.MergedDictionaries>
<local:KinectWindowsViewerSwapCommand x:Key="SwapCommand"/>
</ResourceDictionary>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource SwapCommand}" Executed="Swap_Executed"/>
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Key="Back" Command="{StaticResource SwapCommand}"/>
</Window.InputBindings>
<Grid Name="layoutGrid" Margin="10 0 10 0" TextBlock.FontFamily="{StaticResource KinectFont}">
<Grid.RowDefinitions>
<!-- The title bar -->
<RowDefinition Height="Auto"/>
<!-- The main viewer -->
<RowDefinition Height="*" MinHeight="300"/>
<!-- The audio panel -->
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<!-- The main viewer -->
<ColumnDefinition Width="*" MinWidth="400"/>
<!-- The side panels -->
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<DockPanel Grid.Row="0" Grid.ColumnSpan="2" Margin="10 0 10 20">
<Image DockPanel.Dock="Left" Source="Images\Logo.png" Stretch="None" HorizontalAlignment="Left" Margin="0 10 0 0"/>
<TextBlock DockPanel.Dock="Right"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Foreground="{StaticResource TitleForegroundBrush}" FontSize="{StaticResource LabelFontSize}">Kinect Explorer</TextBlock>
<Image Source="Images\Status.png" Stretch="None" HorizontalAlignment="Center"/>
</DockPanel>
<!-- The main viewer -->
<Grid Grid.Column="0" Grid.Row="1" Margin="10" >
<Grid Name="MainViewerHost">
<Grid Name="ColorVis" Background="{StaticResource DarkNeutralBrush}">
<Viewbox HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="Uniform">
<!-- Make the colorViewer and skeletonViewer overlap entirely. -->
<Grid>
<kt:KinectColorViewer x:Name="ColorViewer" KinectSensorManager="{Binding KinectSensorManager}" CollectFrameRate="True" RetainImageOnSensorChange="True" />
<Canvas>
<kt:KinectSkeletonViewer
KinectSensorManager="{Binding KinectSensorManager}"
Visibility="{Binding KinectSensorManager.ColorStreamEnabled, Converter={StaticResource BoolToVisibilityConverter}}"
Width="{Binding ElementName=ColorViewer,Path=ActualWidth}"
Height="{Binding ElementName=ColorViewer,Path=ActualHeight}"
ImageType="Color" />
</Canvas>
</Grid>
</Viewbox>
<Border
TextBlock.Foreground="{StaticResource LabelForegroundBrush}"
HorizontalAlignment="Right" VerticalAlignment="Top"
Background="{StaticResource MediumNeutralBrush}"
Width="50" Height="50">
<StackPanel Orientation="Vertical" >
<TextBlock FontSize="{StaticResource HeaderFontSize}" Text="{Binding ElementName=ColorViewer, Path=FrameRate}" HorizontalAlignment="Center" Margin="-2"/>
<TextBlock FontSize="{StaticResource FPSFontSize}" HorizontalAlignment="Center" Margin="-2">FPS</TextBlock>
</StackPanel>
</Border>
<Rectangle Fill="#7777" Visibility="{Binding KinectSensorManager.ColorStreamEnabled, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter=True}"/>
</Grid>
</Grid>
</Grid>
<!-- The Audio panel -->
<Grid Grid.Row="2" Grid.Column="0"
Margin="10 10 10 20"
VerticalAlignment="Top">
<kt:KinectAudioViewer
x:Name="kinectAudioViewer"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
KinectSensorManager="{Binding KinectSensorManager}"/>
</Grid>
<!-- The side panels-->
<StackPanel
Orientation="Vertical"
Grid.Column="1"
Grid.Row="1"
Grid.RowSpan="2"
Margin="10"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Grid Name="SideViewerHost" Width="200" Height="150">
<Grid Name="DepthVis" Background="{StaticResource DarkNeutralBrush}">
<Viewbox Stretch="Uniform" HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- Make the depthViewer and skeletonViewer overlap entirely. -->
<Grid>
<kt:KinectDepthViewer
x:Name="DepthViewer"
KinectSensorManager="{Binding KinectSensorManager}"
CollectFrameRate="True"
RetainImageOnSensorChange="True"/>
<Canvas>
<kt:KinectSkeletonViewer
KinectSensorManager="{Binding KinectSensorManager}"
Visibility="{Binding KinectSensorManager.DepthStreamEnabled, Converter={StaticResource BoolToVisibilityConverter}}"
Width="{Binding ElementName=DepthViewer, Path=ActualWidth}"
Height="{Binding ElementName=DepthViewer, Path=ActualHeight}"
ShowBones="true" ShowJoints="true" ShowCenter="true" ImageType="Depth" />
</Canvas>
</Grid>
</Viewbox>
<Border
TextBlock.Foreground="{StaticResource LabelForegroundBrush}"
HorizontalAlignment="Right" VerticalAlignment="Top"
Background="{StaticResource MediumNeutralBrush}"
Width="50" Height="50">
<StackPanel Orientation="Vertical" >
<TextBlock FontSize="{StaticResource HeaderFontSize}" Text="{Binding ElementName=DepthViewer, Path=FrameRate}" HorizontalAlignment="Center" Margin="-2"/>
<TextBlock FontSize="{StaticResource FPSFontSize}" HorizontalAlignment="Center" Margin="-2">FPS</TextBlock>
</StackPanel>
</Border>
<Rectangle Fill="#7777" Visibility="{Binding KinectSensorManager.DepthStreamEnabled, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter=True}"/>
</Grid>
<Button HorizontalAlignment="Left" VerticalAlignment="Top" Command="{StaticResource SwapCommand}">
<Button.Template>
<ControlTemplate>
<Border Width="50" Height="50">
<Border.Style>
<Style>
<Style.Setters>
<Setter Property="Border.Background" Value="{StaticResource MediumNeutralBrush}"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.TemplatedParent}, Path=IsMouseOver}" Value="True">
<Setter Property="Border.Background" Value="{StaticResource DarkNeutralBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Image Source="Images/swap.png"/>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
<kt:KinectSettings KinectSensorManager="{Binding KinectSensorManager}" Margin="0 20 0 0" />
</StackPanel>
<Button Content="Calibrate" Height="23" x:Name="Calibrate" x:FieldModifier="public" Width="75" Margin="213,45,289,435" Grid.RowSpan="2" Click="Calibrate_Click" />
<CheckBox Content="AngleDifference" Height="25" x:Name="AngleDifference" x:FieldModifier="public" Width="135" Margin="0,45,137,433" Grid.RowSpan="2" Checked="AngleDifference_Checked" HorizontalAlignment="Right" Unchecked="AngleDifference_Unchecked" />
</Grid>
</Window>
ViewModel class:
public class ViewModel
{
public bool IsChecked { get; set; }
public bool is_clicked { get; set; }
}
Without seeing the complete KinectWindow.xaml file or downloading the SDK I'm taking a bit of a guess, but it seems to me that your problems are caused by having two different instances of KinectSkeleton:
ks that you're instantiating in KinectWindow, setting its is_checked property and calling setcurrentdt
the one declared somewhere in KinectWindow.xaml in which OnRender is being called.
How to fix this problem?
Find KinectSkeleton in KinectWindow.xaml. It should be defined like this (instead of local, a different namespace prefix could be used):
<local:KinectSkeleton ... />
Give it a name so that you can reference it from code behind in KinextWindow.xaml.cs by adding the x:Name attribute. If you name it ks you won't need to change your existing code modifications:
<local:KinectSkeleton x:Name="ks" ... />
Delete your declaration of KinectSkeleton class in code behind to prevent the clash:
public KinectSkeleton ks = new KinectSkeleton();
Now you will have only a single instance of KinectSkeleton, therefore OnRender will work with the same data that you're modifying from your event handlers.

Mouse scroll not working in a scroll viewer with a wpf datagrid and additional UI elements

I am trying to figure out how to get the mouse scroll working on a wpf window with a scrollviewer and a datagrid within it. The WPF and C# code is below
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Border Name="DataGridBorder" BorderThickness="2" Margin="1" CornerRadius="4" BorderBrush="#FF080757">
<dg:DataGrid AutoGenerateColumns="False" Name="ValuesDataGrid"
BorderThickness="0" CanUserResizeColumns="True" FontWeight="Bold" HorizontalScrollBarVisibility="Auto"
CanUserReorderColumns="False" IsReadOnly="True" IsTextSearchEnabled="True" AlternationCount="2"
SelectionMode="Extended" GridLinesVisibility="All"
HeadersVisibility="Column" CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="False" CanUserSortColumns="False"
RowDetailsVisibilityMode="Collapsed" SelectedIndex="0"
RowStyle="{StaticResource CognitiDataGridRowStyle}"
>
<dg:DataGrid.Columns>
<dg:DataGridTemplateColumn Header="Title" >
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Text="{Binding Path=Name}" FontWeight="Normal" />
</StackPanel>
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>
</dg:DataGrid.Columns>
</dg:DataGrid>
</Border>
</Grid>
<Button Grid.Row="1" Height="90" >hello world</Button>
</Grid>
</ScrollViewer>
and the C# code is as follows
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
initialize();
}
public void initialize()
{
ObservableCollection<MyObject> testList = new ObservableCollection<MyObject>();
for (int i = 0; i < 20; i++)
{
MyObject my = new MyObject("jack " + i);
testList.Add(my);
}
ValuesDataGrid.ItemsSource = testList;
}
}
public class MyObject
{
public string Name { get; set; }
public MyObject(string name)
{
Name = name;
}
}
The problem i am facing is that when using the mouse to scroll, it works fine when it is over the button but as soon as i move the mouse pointer over the grid and try to scroll, nothing happens. I am able to move the scrollbar of the scrollviewer directly though. I am still a wpf novice so any help on how to get the mouse scroll to work over the datagrid would be appreciated. I am guessing there should be a pretty easy solution for this but I havent been able to figure it out
I think Dave's solution is a good one. However, one recommendation I'd make is to catch the PreviewMouseWheel event on the scrollviewer instead of on the datagrid. If you don't, you might notice some minor differences based on whether you're scrolling over the datagrid or the scroll bar itself. The reasoning is that the scrollviewer will be handling scrolling when the mouse is hovered over the scrollbar, and the datagrid event will handle the scrolling when over the datagrid. For instance, one mouse wheel scroll over the datagrid might bring you farther down your list then it would when over the scroll bar. If you catch it on scrollviewer preview event, all will use the same measurement when scrolling. Also, if you catch it this way, you won't need to name the scrollviewer element, as you won't need a reference to the object since you can just type cast the sender object passed into the scrollviewer PreviewMouseWheel event. Lastly, I'd recommend marking the event handled at the end of the event, unless you need to catch it in an element further down the heirarchy for some reason. Example below:
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
ScrollViewer scv = (ScrollViewer)sender;
scv.ScrollToVerticalOffset(scv.VerticalOffset - e.Delta);
e.Handled = true;
}
I'm assuming the DataGrid does not need to scroll - Set the VerticalScrollBar="None" on the DataGrid.
The DataGrid swallows the mouse scroll event.
What I found is to use the PreviewMouseWheel event to scroll the container that you want to scroll. You will need to name the scrollviewer to change the Vertical offset.
private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset-e.Delta);
}
An improvement to Don B's solution is to avoid using ScrollToVerticalOffset.
scv.ScrollToVerticalOffset(scv.VerticalOffset - e.Delta);
VerticalOffset - Delta results in a pretty jarring experience. The ScrollViewer puts a lot of thought into making the movement smoother than that. I think it also scales the delta down based on dpi and other factors...
I found it's better to catch and handle the PreviewMouseWheelEvent and send a MouseWheelEvent to the intended ScrollViewer. My version of Don B's solution looks like this.
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
eventArg.RoutedEvent = UIElement.MouseWheelEvent;
eventArg.Source = e.Source;
ScrollViewer scv = (ScrollViewer)sender;
scv.RaiseEvent(eventArg);
e.Handled = true;
}
To enable touch support you might also want to set ScrollViewer.PanningMode to None on your DataGrid and set the same property to VerticalFirst or other value on your top level ScrollViewer
Example
<ScrollViewer VerticalScrollBarVisibility="Auto" Margin="5" PanningMode="VerticalFirst">
<DataGrid ScrollViewer.PanningMode="None" ItemsSource="{Binding Items}" />
</ScrollViewer>
Of course, also use the PreviewMouseWheel event as indicated by Don B's answers to fix the original mouse scrolling issue.
private static void ScrollViewer_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
var scrollViewer = (ScrollViewer)sender;
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
e.Handled = true;
}
Or, you can simply set the following Attached Property to your ScrollViewer
public class TopMouseScrollPriorityBehavior
{
public static bool GetTopMouseScrollPriority(ScrollViewer obj)
{
return (bool)obj.GetValue(TopMouseScrollPriorityProperty);
}
public static void SetTopMouseScrollPriority(ScrollViewer obj, bool value)
{
obj.SetValue(TopMouseScrollPriorityProperty, value);
}
public static readonly DependencyProperty TopMouseScrollPriorityProperty =
DependencyProperty.RegisterAttached("TopMouseScrollPriority", typeof(bool), typeof(TopMouseScrollPriorityBehavior), new PropertyMetadata(false, OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var scrollViewer = d as ScrollViewer;
if (scrollViewer == null)
throw new InvalidOperationException($"{nameof(TopMouseScrollPriorityBehavior)}.{nameof(TopMouseScrollPriorityProperty)} can only be applied to controls of type {nameof(ScrollViewer)}");
if (e.NewValue == e.OldValue)
return;
if ((bool)e.NewValue)
scrollViewer.PreviewMouseWheel += ScrollViewer_PreviewMouseWheel;
else
scrollViewer.PreviewMouseWheel -= ScrollViewer_PreviewMouseWheel;
}
private static void ScrollViewer_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
var scrollViewer = (ScrollViewer)sender;
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
e.Handled = true;
}
}
Usage
<ScrollViewer b:TopMouseScrollPriorityBehavior.TopMouseScrollPriority="True" VerticalScrollBarVisibility="Auto" Margin="5" PanningMode="VerticalFirst">
<DataGrid ScrollViewer.PanningMode="None" ItemsSource="{Binding Items}" />
</ScrollViewer>
Where b: is the namespace that contains this behavior
This way your no code-behind is needed and your app is purely MVVM
I tried Don B's solution and it's solves the issue with scrolling over a data grid pretty well for the cases when you don't have other inner scrollable controls.
For the case when the scroll viewer has as children other scrollable controls and a data grid, then that requires that the event shouldn't be marked as handled at the end of the event handler for the main scroll viewer so it could be catched also in the inner scrollable controls, however that has the side effect that when the scroll should occur only on the inner scrollable control, it will also occur on the main scroll viewer.
So I've updated Dave's solution with the difference for how the scroll viewer is found so it won't be necessary to know the name of the scroll viewer:
private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
ScrollViewer scrollViewer = (((DependencyObject)sender).GetVisualParent<ScrollViewer>());
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
}
#fjch1997, I used your solution and it works pretty well.
There is only one problem which I found. It's connected with the comment of #Vadim Tofan:
For the case when the scroll viewer has as children other scrollable
controls and a data grid, then that requires that the event shouldn't
be marked as handled at the end of the event handler for the main
scroll viewer so it could be catched also in the inner scrollable
controls, however that has the side effect that when the scroll should
occur only on the inner scrollable control, it will also occur on the
main scroll viewer.
I also tried to remove e.Handled = true statement, but then the effect is not nice - both scrolls are moved in the same time. So, finally I enhanced a little bit event handler method to the following one:
private static void ScrollViewer_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
ScrollViewer scrollViewer = (ScrollViewer)sender;
FrameworkElement origicalControlSender = e.OriginalSource as FrameworkElement;
ScrollViewer closestScrollViewer = origicalControlSender.GetParent<ScrollViewer>();
if (closestScrollViewer != null && !ReferenceEquals(closestScrollViewer, scrollViewer))
{
return;
}
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
e.Handled = true;
}
public static T GetParent<T>(this FrameworkElement control)
where T : DependencyObject
{
FrameworkElement parentElement = control?.Parent as FrameworkElement;
if (parentElement == null)
{
return null;
}
T parent = parentElement as T;
if (parent != null)
{
return parent;
}
return GetParent<T>(parentElement);
}
This now prevents the outside scroller to scroll in case inner ScrollViewer exists.
Guys I`ve seen most of the solution posted over here and fundamentally all of them are right, but I think there is an easiest and more elegant syntax that we could apply.
Assuming that we don't need to scroll with our data grid and instead we would like to scroll with our MainWindow
fallowing "Dave" and "Vladim Tofan" answer
Private Void Scrlll(Object sebder, MouseWheelEventArgs e)
{
var windows = (Window.GetWindow(this) as MainWindow).MainScroll;
windows.ScrollToVerticalOffset(windows.VerticalOffset - e.Delta);
}
Sorry, for bad english.
Here is a larger example of creating a WPF behavior that scrolls the DataGrid.
First define the following base class for gluing together framework element with behavior class and its behavior implementation.
using System.Diagnostics.CodeAnalysis;
using System.Windows;
using System.Windows.Input;
namespace SomeAcme.Client.Infrastructure
{
/// <summary>
/// Behavior handler class for creating WPF behaviors.
/// </summary>
[ExcludeFromCodeCoverage]
public class BehaviorHandler<TAssociatedObject, TBehaviorClass>
where TAssociatedObject: DependencyObject
where TBehaviorClass : class, IAssociationBehavior, new()
{
public BehaviorHandler()
{
}
public static TBehaviorClass GetBehavior(DependencyObject obj)
{
if (obj == null)
return null;
return (TBehaviorClass)obj.GetValue(BehaviorProperty);
}
public static void SetBehavior(DependencyObject obj, TBehaviorClass value)
{
if (obj != null)
{
obj.SetValue(BehaviorProperty, value);
}
}
// Using a DependencyProperty as the backing store for Behavior. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BehaviorProperty =
DependencyProperty.RegisterAttached("Behavior", typeof(TBehaviorClass), typeof(object), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));
public void FindOrCreateBehaviorOnDemand(DependencyObject dependencyObject)
{
//Apply the behavior
TBehaviorClass behavior = FindOrCreateBehavior(dependencyObject);
if (behavior != null)
{
dependencyObject.SetValue(BehaviorProperty, behavior);
}
}
public TBehaviorClass FindOrCreateBehavior(DependencyObject dependencyObject)
{
if (dependencyObject == null)
return null;
var associatedObject = dependencyObject;
if (associatedObject == null)
return null;
var behavior = dependencyObject.GetValue(BehaviorProperty) as TBehaviorClass;
if (behavior == null)
{
var behaviorAssociated = new TBehaviorClass();
if (behaviorAssociated == null)
return null;
behaviorAssociated.InitializeAssociation(associatedObject, AssociatedCommands);
return behaviorAssociated;
}
else
{
return behavior;
}
} //TBehaviorClass FindOrCreateBehavior
/// <summary>
/// Set the associated commands to pass into the WPF behavior, if desired
/// </summary>
public ICommand[] AssociatedCommands { get; set; }
}
}
This looks a bit more complex, but it simplifies boiler plate coding when it comes to gluing together the behavior class with the framework element. In this particular case we will not need to use an associated command.
Our scroll viewer behavior then looks like this:
using SomeAcme.Client.Infrastructure;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SomeAcme.Client.Infrastructure
{
public static class ScrollViewerMouseWheelScrollBehavior
{
public static string GetUseMouseScrollForScrollViewer(DependencyObject obj)
{
if (obj != null)
{
return (string)obj.GetValue(UseMouseScrollForScrollViewerProperty);
}
else
return null;
}
public static void SetUseMouseScrollForScrollViewer(DependencyObject obj, string value)
{
if (obj != null)
{
obj.SetValue(UseMouseScrollForScrollViewerProperty, value);
}
}
public static readonly DependencyProperty UseMouseScrollForScrollViewerCommandProperty =
DependencyProperty.RegisterAttached("UseScrollForScrollViewer", typeof(string), typeof(ScrollViewerMouseWheelScrollBehavior),
new PropertyMetadata(new PropertyChangedCallback(OnUseScrollForScrollViewerSet)));
public static void OnUseScrollForScrollViewerSet(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
bool useScroll;
bool.TryParse(eventArgs.NewValue?.ToString(), out useScroll);
if (useScroll)
{
var behaviorHandler =
new BehaviorHandler<ScrollViewer, ScrollViewerMouseScrollBehaviorImplementation>();
//behaviorHandler.AssociatedCommands =
// new Microsoft.Practices.Prism.Commands.DelegateCommand<object>[] { (Microsoft.Practices.Prism.Commands.DelegateCommand<object>)eventArgs.NewValue };
behaviorHandler.FindOrCreateBehaviorOnDemand(dependencyObject);
}
}
}
}
namespace SomeAcme.Client.Infrastructure
{
public class ScrollViewerMouseScrollBehaviorImplementation : IAssociationBehavior
{
public void InitializeAssociation(DependencyObject associatedObject, params ICommand[] commands)
{
//TODO: Add commands to associate
var scrollViewer = associatedObject as ScrollViewer;
if (scrollViewer != null)
{
scrollViewer.PreviewMouseWheel += ScrollViewer_PreviewMouseWheel;
}
}
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var scrollViewer = sender as ScrollViewer;
if (scrollViewer != null)
{
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
}
}
}
}
In XAML we then can first define the namespaces:
xmlns:local="clr-namespace:SomeAcme.Client.Infrastructure"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Finally we are ready to use the mouse wheel scroll by using the WPF behavior in the XAML.
..
<TabControl Grid.Row="1">
<TabItem Header="Skjemafelter">
<ScrollViewer Height="700" local:ScrollViewerMouseWheelScrollBehavior.UseMouseScrollForScrollViewer="{x:Static sys:Boolean.TrueString}">
<ListView x:Name="ColumnsListView" ItemsSource="{Binding CurrentFields}">
<ListView.View>
<GridView>
I have tested and verified that this approach works. For developers working with a WPF app, utilizing WPF behaviors keeps amount of code in code behind still to the minimum bits and staying faitfully to the MVVM approach.
The Grid has a built in ScrollPanel so wrapping it with a ScrollPanel didn't work at all for me (making the Grid of fixed height was out of the question because I wanted it to resize nicely with the rest of the application).
What I did was a combination of a few of the higher rated solutions in here, but essentially the idea is to get rid of the ScrollPanel and just pipe the PreviewMouseEvent of the DataGrid back to the parent control that is actually handling the scrolling (in my case it was a Grid).
private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
MouseWheelEventArgs eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = MouseWheelEvent, Source = e.Source
};
DependencyObject parent = VisualTreeHelper.GetParent((DependencyObject) sender);
while (parent != null && !(parent is Grid))
{
parent = VisualTreeHelper.GetParent(parent);
}
if (parent != null)
{
Grid grid = (Grid) parent;
grid.RaiseEvent(eventArg);
}
e.Handled = true;
}
I found this: http://wpfthoughts.blogspot.com/2014/05/datagrid-vertical-scrolling-issues.html and thought I should add it here.
"If you are targeting Framework 4.5 there is a new dependency object on the DataGrid's internal VirtualizingPanel called ScrollUnit that can be set to Item (the default) or Pixel. If we modify the XAML a little we can see how it works."
<DataGrid Name="DG" ItemsSource="{Binding B0}" AutoGenerateColumns="False" IsReadOnly="true" RowDetailsVisibilityMode="Visible" Width="200"
Height="100" VirtualizingPanel.ScrollUnit="Pixel">
I know it's been a while, but I met the same issue and solved it with DockPanel
<DockPanel Grid.ColumnSpan="3" LastChildFill="True">
<Button DockPanel.Dock="Top" Content="Add" Grid.Column="3" HorizontalAlignment="Right" Width="110" Height="30" Margin="5"/>
<DataGrid x:Name="xxDG" SelectionUnit="Cell" ItemsSource="{Binding}" Margin="0, 0, 0, 0" >
...
</DataGrid>
</DockPanel>
For some reason this handles the mouse wheel events like a charm.
I found that ScrollViewer can't be concatenated, which means if it is concatenated like in your case the Grid starts under the ScrollViewer tag and in the Grid we have DataGrid and in the DataGrid again the ScrollViewer property has been set. i.e.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="45" />
<RowDefinition Height="100*" />
<RowDefinition Height="105" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0"
Grid.Column="0"
Margin="10,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontWeight="Bold"
Content="Vessel: " />
<TextBox Height="30"
Width="300"
Margin="70,0,0,0"
HorizontalAlignment="Left"
BorderThickness="1,1,1,1"
IsReadOnly="True"
Name="txtVessel" />
<Label Grid.Row="0"
Grid.Column="2"
Margin="0,0,185,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontWeight="Bold"
Content="Month:" />
<StackPanel Orientation="Horizontal"
Grid.Row="0"
Grid.Column="2"
Margin="0,0,0,0"
HorizontalAlignment="Right">
<ComboBox BorderThickness="2"
HorizontalAlignment="Right"
Name="CmbMonth"
VerticalAlignment="Center"
Width="90" />
<ComboBox BorderThickness="2"
HorizontalAlignment="Right"
Margin="5,0,0,0"
Name="CmbYear"
VerticalAlignment="Center"
Width="90" />
</StackPanel>
<Grid Grid.Row="1"
Grid.ColumnSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="45" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="220" />
<ColumnDefinition Width="80" />
<ColumnDefinition Width="80" />
<ColumnDefinition Width="80" />
<ColumnDefinition Width="80" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="140*" />
</Grid.ColumnDefinitions>
<Border BorderBrush="Black"
BorderThickness="0,1,1,1"
Grid.Row="0"
Grid.Column="0" />
<Border BorderBrush="Black"
BorderThickness="0,1,1,1"
Grid.Row="0"
Grid.Column="1" />
<Border BorderBrush="Black"
BorderThickness="0,1,1,1"
Grid.Row="0"
Grid.Column="2" />
<Border BorderBrush="Black"
BorderThickness="0,1,1,1"
Grid.Row="0"
Grid.Column="3" />
<Border BorderBrush="Black"
BorderThickness="0,1,1,1"
Grid.Row="0"
Grid.Column="4" />
<Border BorderBrush="Black"
BorderThickness="0,1,1,1"
Grid.Row="0"
Grid.Column="5" />
<Border BorderBrush="Black"
BorderThickness="0,1,1,1"
Grid.Row="0"
Grid.Column="6" />
<Border BorderBrush="Black"
BorderThickness="0,1,1,1"
Grid.Row="0"
Grid.Column="7" />
<Border BorderBrush="Black"
BorderThickness="0,1,1,1"
Grid.Row="0"
Grid.Column="8" />
<Label Grid.Row="0"
Grid.Column="1"
Margin="0,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Content="Item" />
<Label Grid.Row="0"
Grid.Column="2"
Margin="0,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Content="Maker" />
<Label Grid.Row="0"
Grid.Column="3"
Margin="0,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Content="Model" />
<Label Grid.Row="0"
Grid.Column="4"
Margin="0,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Content=" Part No.
Serial No." />
<Label Grid.Row="0"
Grid.Column="5"
Margin="0,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Content="Condition" />
<Label Grid.Row="0"
Grid.Column="6"
Margin="0,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Content=" Onboard
Calibr/Test" />
<Label Grid.Row="0"
Grid.Column="7"
Margin="0,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Content=" Shore
Callibration" />
<Label Grid.Row="0"
Grid.Column="8"
Margin="0,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Content="Remarks" />
</Grid>
<Border Grid.Row="2"
Grid.ColumnSpan="2">
<ScrollViewer Grid.Row="2"
Grid.ColumnSpan="2"
CanContentScroll="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
Name="ScrollViewer3"
Margin="0,0,0,0">
<Grid Name="grdOnBoardCalibrationRecord"
Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"></ColumnDefinition>
<ColumnDefinition Width="220"></ColumnDefinition>
<ColumnDefinition Width="80"></ColumnDefinition>
<ColumnDefinition Width="80"></ColumnDefinition>
<ColumnDefinition Width="80"></ColumnDefinition>
<ColumnDefinition Width="80"></ColumnDefinition>
<ColumnDefinition Width="120"></ColumnDefinition>
<ColumnDefinition Width="120"></ColumnDefinition>
<ColumnDefinition Width="140*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
BorderThickness="1,0,1,1"
BorderBrush="Black"
Grid.RowSpan="26"></Border>
<Border Grid.Column="1"
BorderThickness="0,1,1,1"
Grid.RowSpan="26"></Border>
<Border Grid.Column="2"
BorderThickness="0,1,1,1"
Grid.RowSpan="26"></Border>
<Border Grid.Column="3"
BorderThickness="0,1,1,1"
Grid.RowSpan="26"></Border>
<Border Grid.Column="4"
BorderThickness="0,1,1,1"
Grid.RowSpan="26"></Border>
<Border Grid.Column="5"
BorderThickness="0,1,1,1"
Grid.RowSpan="26"></Border>
<Border Grid.Column="6"
BorderThickness="0,1,1,1"
Grid.RowSpan="26"></Border>
<Border Grid.Column="7"
BorderThickness="0,1,1,1"
Grid.RowSpan="26"></Border>
<Border Grid.Column="8"
BorderThickness="0,1,1,1"
Grid.RowSpan="26"></Border>
</Grid>
</ScrollViewer>
</Border>
<Grid Grid.Row="3"
Grid.ColumnSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox Grid.Row="0"
Grid.Column="0"
Height="30"
Width="300"
TextAlignment="Center"
Background="Gray"
IsReadOnly="True"
Margin="0,0,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
BorderThickness="1,1,1,1"
Name="txtChiefEngineer" />
<Label Grid.Row="1"
Grid.Column="1"
Margin="0,0,100,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontWeight="Bold"
Content="Chief Engineer" />
<StackPanel Orientation="Horizontal"
Grid.Row="2"
Margin="0,0,0,0">
<Label Name="lblonshorecomment"
Content=" Shore Comment : "
HorizontalAlignment="Center"
Margin="5,0,0,0"
FontWeight="Bold"
VerticalAlignment="Center"
FontFamily="Calibri"
FontStyle="Normal"
FontSize="14"></Label>
<TextBox BorderThickness="1"
FontWeight="Normal"
IsReadOnly="True"
Height="44"
Width="878"
TextWrapping="Wrap"
AcceptsReturn="True"
HorizontalAlignment="left"
Margin="0,0,0,0"
Name="txtShoreComment"
VerticalAlignment="Center" />
</StackPanel>
</Grid>
</Grid>

Categories

Resources