Get selected controls from Pivot.ItemTemplate - xaml

I have a XAML:
<phone:Pivot Name="pivot" SelectionChanged="pivot_SelectionChanged">
<phone:Pivot.ItemTemplate>
<DataTemplate>
<ViewportControl Name="viewport">
<Canvas Name="canvas">
<Image Name="image"
RenderTransformOrigin="0,0"
CacheMode="BitmapCache"
Source="{Binding ImageSource}">
<Image.RenderTransform>
<ScaleTransform x:Name="xform"/>
</Image.RenderTransform>
</Image>
</Canvas>
</ViewportControl>
</DataTemplate>
</phone:Pivot.ItemTemplate>
</phone:Pivot>
Can I get current ViewportControl, Canvas, etc for selected item? For example
private void pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//get ViewportControl, Canvas, etc
}
Not necessarily with SelectionChanged, may have other solutions?

Yes, but you have to search for them. You need to use VisualTreeHelper and hunt for children of the desired type.
Or you can have some mapping in the code-behind for all controls and the parent pivot items.

Related

How to bind InkCanvas height and width to an image

I have an Image control in a Grid that is displayed when the user clicks on an image in a list. I want to add an InkCanvas control directly on top of the Image control so the user can draw on it.
However, it seems like the height and width of the InkCanvas is not being bound correctly to the image and I am able to draw outside the image. What else do I need to do?
My XAML code:
<Grid>
<Image x:Name="result_img" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<InkCanvas x:Name="inkCanvas" x:Load="False" Height="{x:Bind result_img.ActualHeight}" Width="{x:Bind result_img.ActualWidth}"/>
</Grid>
Code-behind (C++/CX):
void MyGui::test::ListView_ItemClick(Platform::Object^ sender, Windows::UI::Xaml::Controls::ItemClickEventArgs^ e)
{
this->FindName("inkCanvas");
inkCanvas->InkPresenter->InputDeviceTypes = CoreInputDeviceTypes::Mouse;
}
Any help would be much appreciated!
If you want to use element-to-element binding,you should use the ElementName property.The ElementName is the name of the control you want to bind and the Path is the property of the control you want to bind.
<Image x:Name="result_img" VerticalAlignment="Center" HorizontalAlignment="Center" Width="300" Height="400" />
<InkCanvas x:Name="inkCanvas" x:Load="False" Height="{Binding ElementName=result_img, Path=ActualHeight}" Width="{Binding ElementName=result_img, Path=ActualWidth}"/>

MasterDetail ListView and editable ContentPresenter: what is wrong?

I'm based on the official Microsoft sample to create a MasterDetail ListView:
MasterDetail ListView UWP sample
I have adapted it to my case, as I want that users can edit directly selected items from the ListView. But I meet a strange comportement:
when I add a new item to the ListView, the changes of the current item, done in the details container, are well saved
but when I select an existing item in the ListView, the changes of the current item, done in the details container, are not saved
Here is a screenshot of my app:
The XAML of my ListView is like this:
<!-- Master : List of Feedbacks -->
<ListView
x:Name="MasterListViewFeedbacks"
Grid.Row="1"
ItemContainerTransitions="{x:Null}"
ItemTemplate="{StaticResource MasterListViewFeedbacksItemTemplate}"
IsItemClickEnabled="True"
ItemsSource="{Binding CarForm.feedback_comments}"
SelectedItem="{Binding SelectedFeedback, Mode=TwoWay}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.FooterTemplate>
<DataTemplate>
<CommandBar Background="White">
<CommandBar.Content>
<StackPanel Orientation="Horizontal">
<AppBarButton Icon="Add" Label="Add Feedback"
Command="{Binding AddItemFeedbacksCommand}" />
<AppBarButton Icon="Delete" Label="Delete Feedback"
Command="{Binding RemoveItemFeedbacksCommand}" />
</StackPanel>
</CommandBar.Content>
</CommandBar>
</DataTemplate>
</ListView.FooterTemplate>
</ListView>
The XAML of the ListView's ItemTemplate is:
<DataTemplate x:Key="MasterListViewFeedbacksItemTemplate" x:DataType="models:Feedback_Comments">
<StackPanel Margin="0,11,0,13"
Orientation="Horizontal">
<TextBlock Text="{x:Bind creator }"
Style="{ThemeResource BaseTextBlockStyle}" />
<TextBlock Text=" - " />
<TextBlock Text="{x:Bind comment_date }"
Margin="12,1,0,0" />
</StackPanel>
</DataTemplate>
The XAML of the Details container is like this:
<!-- Detail : Selected Feedback -->
<ContentPresenter
x:Name="DetailFeedbackContentPresenter"
Grid.Column="1"
Grid.RowSpan="2"
BorderThickness="1,0,0,0"
Padding="24,0"
BorderBrush="{ThemeResource SystemControlForegroundBaseLowBrush}"
Content="{x:Bind MasterListViewFeedbacks.SelectedItem, Mode=OneWay}">
<ContentPresenter.ContentTemplate>
<DataTemplate x:DataType="models:Feedback_Comments">
<StackPanel Visibility="{Binding FeedbacksCnt, Converter={StaticResource CountToVisibilityConverter}}">
<TextBox Text="{Binding creator, Mode=TwoWay}" />
<DatePicker Date="{Binding comment_date, Converter={StaticResource DateTimeToDateTimeOffsetConverter}, Mode=TwoWay}"/>
<TextBox TextWrapping="Wrap" AcceptsReturn="True" IsSpellCheckEnabled="True"
Text="{Binding comment, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ContentPresenter.ContentTemplate>
<ContentPresenter.ContentTransitions>
<!-- Empty by default. See MasterListView_ItemClick -->
<TransitionCollection />
</ContentPresenter.ContentTransitions>
</ContentPresenter>
The "CarForm" is the main object of my ViewModel. Each CarForm contains a List of "Feedback_Comments".
So in my ViewModel, I do this when I add a new comment:
private void AddItemFeedbacks()
{
FeedbacksCnt++;
CarForm.feedback_comments.Add(new Feedback_Comments()
{
sequence = FeedbacksCnt,
creator_id = user_id,
_creator = username,
comment_date = DateTime.Now
});
SelectedFeedback = CarForm.feedback_comments[CarForm.feedback_comments.Count - 1];
}
=> the changes done in the Feedback_Comment that was edited before the add are well preserved
I don't do anything when the user select an existing Feedback_Comment: this is managed by the XAML directly.
=> the changes done in the Feedback_Comment that was edited before to select anoter one are not preserved
=> Would you have any explanation?
The TwoWay binding for the Text property is updated only when the TextBox loses focus. However, when you select a different item in the list, the contents of the TextBox are no longer bound to the original item and so are not updated.
To trigger the update each time the Text contents change, so that the changes are reflected immediately, set the UpdateSourceTrigger set to PropertyChanged:
<TextBox Text="{Binding comment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Triggering changes everywhere
To ensure your changes are relflected everywhere including the list, you will need to do two things.
First, your feedback_comments is of type ObservableCollection<Feedback_Comments>. This ensures that the added and removed items are added and removed from the ListView.
Second, the Feedback_Comments class must implement the INotifyPropertyChanged interface. This interface is required to let the user interface know about changes in the data-bound object properties.
Implementing this interface is fairly straightforward and is described for example on MSDN.
The quick solution looks like this:
public class Feedback_Comments : INotifyPropertyChanged
{
// your code
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged( [ CallerMemberName ]string propertyName = "" )
{
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
}
Now from each of your property setters call OnPropertyChanged(); after setting the value:
private string _comment = "";
public string Comment
{
get
{
return _comment;
}
set
{
_comment = value;
OnPropertyChanged();
}
}
Note, that the [CallerMemberName] attribute tells the compiler to replace the parameter by the name of the caller - in this case the name of the property, which is exactly what you need.
Also note, that you can't use simple auto-properties in this case (because you need to call the OnPropertyChanged method.
Bonus
Finally as a small recommendation, I see you are using C++-like naming conventions, which does not fit too well into the C# world. Take a look at the recommended C# naming conventions to improve the code readability :-) .

Binding shared flyout to 2 controls in Listview's DataTemplate in UWP Windows 10

I've got a shared Flyout defined in my <Page.Resources> as follows:
<Flyout x:Name="InfoFlyout" Opened="{Binding IsOpen,
ElementName=MyListView, Mode=TwoWay}">
<Grid>
<Button Foreground="White" Margin="5">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Help"/>
</StackPanel>
</Button>
</Grid>
</Flyout>
But I get An object reference not set error when compiling, so I used the code from this article (Using Windows 8.1 Flyout control with MVVM) instead.
This seems to circumvent the problem I was having with the above code. Now my shared Flyout code looks like this:
<Flyout x:Name="InfoFlyout"
helpers:FlyoutHelpers.Parent="{Binding ElementName=MyListView}"
helpers:FlyoutHelpers.IsOpen="{Binding IsOpen, Mode=TwoWay}">
<Grid>
<Button Foreground="White" Margin="5">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Help"/>
</StackPanel>
</Button>
</Grid>
</Flyout>
My ListView control (i.e. x:Name="MyListView") is binded to the page's ViewModel i.e. MainPageViewModel. The IsOpen property is defined in the MainViewModel.
Now in my ListView DataTemplate, I want my Flyout to open when I press and hold the ListViewItem or when pressing a button within the ListViewItem:
<DataTemplate>
<Grid FlyoutBase.AttachedFlyout="{StaticResource InfoFlyout}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source={Binding MyImage} />
<Grid Grid.Column="1" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Width="30" Height="30"
Flyout="{StaticResource InfoFlyout}"
content="i">
</Button>
</Grid>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Holding">
<actions:OpenFlyoutAction />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Grid>
</DataTemplate>
As you can see, I've got the Flyout "attached" to the Grid via:
FlyoutBase.AttachedFlyout="{StaticResource InfoFlyout}"
and I've got the same Flyout attached to the button within the ListViewItem itself via:
Flyout="{StaticResource InfoFlyout}"
I've put breakpoints on both my setter and getter for the IsOpen property and when page gets loaded, it does go into the getter but whenever I open or close my Flyout either via Holding or by pressing the 'i' button, it doesn't trigger the method below and therefore it doesn't change the IsOpen property.
private static void OnIsOpenPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) as defined in the FlyoutHelper class.
The reason I've set my ElementName to MyListView is that I want all my ListViewItem to be binded to the one property i.e. IsOpen as I need to detect whenever a flyout menu is opened irrelevant of which ListViewItem it belongs to.
How can I achieve or resolve this?
UPDATE - 1
The problem of accessing the shared menu has been resolved by using the following:
<Flyout x:Name="InfoFlyout"
helpers:FlyoutHelpers.Parent="{Binding ElementName=MyListView}"
helpers:FlyoutHelpers.IsOpen="{Binding IsOpen, Mode=TwoWay}">
and setting the button to
<Button Width="30" Height="30"
Command="{Binding InformationCommand}"
CommandParameter="{Binding}"
Flyout="{StaticResource InfoFlyout}">
Which is fine and as #ElvisXia mentioned, you can comment out the code in the OnIsOpenPropertyChanged as the positioning is already determined by the button located inside my ListViewItem.
There is however one outstanding problem. A small one btw, but nice if it can be solved. The shared flyout which is attached to the grid itself in the DataTemplate i.e.
<DataTemplate>
<Grid FlyoutBase.AttachedFlyout="{StaticResource InfoFlyout}">
It is being positioning based on the ListViewItem which technically is correct as I'm calling a different piece of code for that one i.e.
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Holding">
<actions:OpenFlyoutAction />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
And the OpenFlyoutAction is defined as follows:
public class OpenFlyoutAction : DependencyObject, IAction
{
public object Execute(object sender, object parameter)
{
FrameworkElement senderElement = sender as FrameworkElement;
FlyoutBase flyoutBase = FlyoutBase.GetAttachedFlyout(senderElement);
flyoutBase.ShowAt(senderElement);
return null;
}
}
Can I somehow stop using the OpenFlyoutAction and use the same code as provided in the article to open my Flyout wherever the user is holding his/her finger on the relevant ListViewItem rather than on top or below the actual ListViewItem?
I understand it's a little bit side track from the original issue which was to share a Flyout by to controls but may as well finish it as it is somehow relevant to the issue.
Thanks.
Change the type of Parent from Button to ListView. To open flyout in particular X,Y position is not possible in WP. You can choose PopUp control instead. Here is a link which i got open the pop up in tapped position. You can use VisualTreeHelper to get PopUp control of tapped ListViewItem
By Using Windows 8.1 Flyout control with MVVM , the author use parent to control where the flyout shows up.
So the author have codes like below(FlyoutHelpers.cs):
private static void OnIsOpenPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var flyout = d as Flyout;
var parent = (ListView)d.GetValue(ParentProperty);
if (flyout != null && parent != null)
{
var newValue = (bool)e.NewValue;
if (newValue)
flyout.ShowAt(parent);
else
flyout.Hide();
}
}
He use flyout.ShowAt(parent) to let flyout show at parent element. But in your codes you have binded the flyout to the button using:
<Button Width="30" Height="30"
Flyout="{StaticResource InfoFlyout}" content="i">
</Button>
So it is not necessary to let it show at it's parent any more. To fix the problem, you can comment out the statements like below:
private static void OnIsOpenPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
//var flyout = d as Flyout;
//var parent = (ListView)d.GetValue(ParentProperty);
//if (flyout != null && parent != null)
//{
// var newValue = (bool)e.NewValue;
// if (newValue)
// flyout.ShowAt(parent);
// else
// flyout.Hide();
//}
}
Then you will see the flyout shows at the right place.

watermarked PasswordBox in winrt

is it possible to get a watermarks passwordbox in WinRt? It is no problem to get a textbox with a watermark, but I don't know a toolkit where I can get a password box with a watermark.
How can I implement one for myself?
Take a look on WinRT XAML Toolkit.
They also have
WatermarkTextBox
WatermarkPasswordBox
By yourself you can implement your own controls:
in .xaml:
<Border x:Name="brdPassword" Margin="5,0,5,10" BorderThickness="2" BorderBrush="White" CornerRadius="5" Grid.Row="0"
Background="White" Height="50" VerticalAlignment="Stretch">
<Grid>
<TextBox x:Name="PasswordWatermark" TextWrapping="Wrap"
Text="Watermark" Foreground="#FFC4C4C4" IsHitTestVisible="False"
Background="{x:Null}" BorderThickness="0" Padding="0,-10"
FontSize="26.667" />
<PasswordBox x:Name="pbPassword" LostFocus="PasswordLostFocus"
GotFocus="PasswordGotFocus" Background="{x:Null}"
FontSize="26.667" Margin="0,-12,0,-9" VerticalAlignment="Center"
BorderThickness="0" Opacity="0" />
</Grid>
</Border>
in .cs
private void PasswordLostFocus(object sender, RoutedEventArgs e)
{
CheckPasswordWatermark();
}
private void CheckPasswordWatermark()
{
var passwordEmpty = string.IsNullOrEmpty(pbPassword.Password);
PasswordWatermark.Opacity = passwordEmpty ? 100 : 0;
pbPassword.Opacity = passwordEmpty ? 0 : 100;
}
private void PasswordGotFocus(object sender, RoutedEventArgs e)
{
PasswordWatermark.Opacity = 0;
pbPassword.Opacity = 100;
}
Hope it's help
I don't think we can put watermark in the Password control.
You can put a TextBox with wartermark in the same row and same column with the Password control, then handle the two controls' GotFocus and LostFocus events to make the control Visible or Collapsed.
There is no toolkit yet which provides watermarked password box. However this may help:-
http://code.msdn.microsoft.com/windowsdesktop/Watermarked-TextBox-and-444ebdec
Also, check out http://julmar.com/blog/mark/?p=300 for both a Textbox and PasswordBox implementation for WinRT.

Enabling ScrollViewer HorizontalSnapPoints with bindable collection

I'm trying to create a similar experience as in the ScrollViewerSample from the Windows 8 SDK samples to be able to snap to the items inside a ScrollViewer when scrolling left and right. The implementation from the sample (which works) is like this:
<ScrollViewer x:Name="scrollViewer" Width="480" Height="270"
HorizontalAlignment="Left" VerticalAlignment="Top"
VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto"
ZoomMode="Disabled" HorizontalSnapPointsType="Mandatory">
<StackPanel Orientation="Horizontal">
<Image Width="480" Height="270" AutomationProperties.Name="Image of a cliff" Source="images/cliff.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of Grapes" Source="images/grapes.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of Mount Rainier" Source="images/Rainier.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a sunset" Source="images/sunset.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a valley" Source="images/valley.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</StackPanel>
</ScrollViewer>
The only difference with my desired implementation is that I don't want a StackPanel with items inside, but something I can bind to. I am trying to accomplish this with an ItemsControl, but for some reason the Snap behavior does not kick in:
<ScrollViewer x:Name="scrollViewer" Width="480" Height="270"
HorizontalAlignment="Left" VerticalAlignment="Top"
VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto"
ZoomMode="Disabled" HorizontalSnapPointsType="Mandatory">
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a cliff" Source="images/cliff.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of Grapes" Source="images/grapes.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of Mount Rainier" Source="images/Rainier.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a sunset" Source="images/sunset.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image Width="480" Height="270" AutomationProperties.Name="Image of a valley" Source="images/valley.jpg" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</ItemsControl>
</ScrollViewer>
Suggestions would be greatly appreciated!
Thanks to Denis, I ended up using the following Style on the ItemsControl and removed the ScrollViewer and inline ItemsPanelTemplate altogether:
<Style x:Key="ItemsControlStyle" TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<ScrollViewer Style="{StaticResource HorizontalScrollViewerStyle}" HorizontalSnapPointsType="Mandatory">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Getting snap points to work for bound collections can be tricky. For snap points to work immediate child of ScrollViewer should implement IScrollSnapPointsInfo interface. ItemsControl doesn't implement IScrollSnapPointsInfo and consequently you wouldn't see snapping behaviour.
To work around this issue you got couple options:
Create custom class derived from ItemsControl and implement IScrollSnapPointsInfo interface.
Create custom style for items control and set HorizontalSnapPointsType property on ScrollViewer inside the style.
I've implemented former approach and can confirm that it works, but in your case custom style could be a better choice.
Ok, here is the simplest (and standalone) example for horizontal ListView with binded items and correctly working snapping (see comments in following code).
xaml:
<ListView x:Name="YourListView"
ItemsSource="{x:Bind Path=Items}"
Loaded="YourListView_OnLoaded">
<!--Set items panel to horizontal-->
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<!--Some item template-->
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
background code:
private void YourListView_OnLoaded(object sender, RoutedEventArgs e)
{
//get ListView
var yourList = sender as ListView;
//*** yourList style-based changes ***
//see Style here https://msdn.microsoft.com/en-us/library/windows/apps/mt299137.aspx
//** Change orientation of scrollviewer (name in the Style "ScrollViewer") **
//1. get scrollviewer (child element of yourList)
var sv = GetFirstChildDependencyObjectOfType<ScrollViewer>(yourList);
//2. enable ScrollViewer horizontal scrolling
sv.HorizontalScrollMode =ScrollMode.Auto;
sv.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
sv.IsHorizontalRailEnabled = true;
//3. disable ScrollViewer vertical scrolling
sv.VerticalScrollMode = ScrollMode.Disabled;
sv.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
sv.IsVerticalRailEnabled = false;
// //no we have horizontally scrolling ListView
//** Enable snapping **
sv.HorizontalSnapPointsType = SnapPointsType.MandatorySingle; //or you can use SnapPointsType.Mandatory
sv.HorizontalSnapPointsAlignment = SnapPointsAlignment.Near; //example works only for Near case, for other there should be some changes
// //no we have horizontally scrolling ListView with snapping and "scroll last item into view" bug (about bug see here http://stackoverflow.com/questions/11084493/snapping-scrollviewer-in-windows-8-metro-in-wide-screens-not-snapping-to-the-las)
//** fix "scroll last item into view" bug **
//1. Get items presenter (child element of yourList)
var ip = GetFirstChildDependencyObjectOfType<ItemsPresenter>(yourList);
// or var ip = GetFirstChildDependencyObjectOfType<ItemsPresenter>(sv); //also will work here
//2. Subscribe to its SizeChanged event
ip.SizeChanged += ip_SizeChanged;
//3. see the continuation in: private void ip_SizeChanged(object sender, SizeChangedEventArgs e)
}
public static T GetFirstChildDependencyObjectOfType<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj is T) return depObj as T;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = GetFirstChildDependencyObjectOfType<T>(child);
if (result != null) return result;
}
return null;
}
private void ip_SizeChanged(object sender, SizeChangedEventArgs e)
{
//3.0 if rev size is same as new - do nothing
//here should be one more condition added by && but it is a little bit complicated and rare, so it is omitted.
//The condition is: yourList.Items.Last() must be equal to (yourList.Items.Last() used on previous call of ip_SizeChanged)
if (e.PreviousSize.Equals(e.NewSize)) return;
//3.1 get sender as our ItemsPresenter
var ip = sender as ItemsPresenter;
//3.2 get the ItemsPresenter parent to get "viewable" width of ItemsPresenter that is ActualWidth of the Scrollviewer (it is scrollviewer actually, but we need just its ActualWidth so - as FrameworkElement is used)
var sv = ip.Parent as FrameworkElement;
//3.3 get parent ListView to be able to get elements Containers
var yourList = GetParent<ListView>(ip);
//3.4 get last item ActualWidth
var lastItem = yourList.Items.Last();
var lastItemContainerObject = yourList.ContainerFromItem(lastItem);
var lastItemContainer = lastItemContainerObject as FrameworkElement;
if (lastItemContainer == null)
{
//NO lastItemContainer YET, wait for next call
return;
}
var lastItemWidth = lastItemContainer.ActualWidth;
//3.5 get margin fix value
var rightMarginFixValue = sv.ActualWidth - lastItemWidth;
//3.6. fix "scroll last item into view" bug
ip.Margin = new Thickness(ip.Margin.Left,
ip.Margin.Top,
ip.Margin.Right + rightMarginFixValue, //APPLY FIX
ip.Margin.Bottom);
}
public static T GetParent<T>(DependencyObject reference) where T : class
{
var depObj = VisualTreeHelper.GetParent(reference);
if (depObj == null) return (T)null;
while (true)
{
var depClass = depObj as T;
if (depClass != null) return depClass;
depObj = VisualTreeHelper.GetParent(depObj);
if (depObj == null) return (T)null;
}
}
About this example.
Most of checks and errors handling is omitted.
If you override ListView Style/Template, VisualTree search parts must be changed accordingly
I'd rather create inherited from ListView control with this logic, than use provided example as-is in real code.
Same code works for Vertical case (or both) with small changes.
Mentioned snapping bug - ScrollViewer bug of handling SnapPointsType.MandatorySingle and SnapPointsType.Mandatory cases. It appears for items with not-fixed sizes
.