Hiding and displaying again a TreeView modifies which TreeViewItem is selected. Why? - silverlight-4.0

I have a TreeView in SL4 which is already populated, and with a certain TreeViewItem selected.
Just doing
treeview.Visibility=Visibility.Collapsed;
and then
treeview.Visibility=Visibility.Visible;
modifies which TreeViewItem is selected. Why? Is there a way to avoid that?
EDIT 2012.02.04 03:29
I've traced the problem down to its root. This is the (for me, astonishing) conclusion of what I've found: if all of the following conditions are met:
1) the TreeView has two levels (or more) of depth,
2) I start by I expanding all of its nodes,
3) I start selecting a node at the second level, and
4) (this is the weirdest one) the button I use to collapse and show again the TreeView is NOT a tab stop,
then the TreeView does NOT keep its selection after being collapsed and shown again!!!
Please download the small solution+sample that reproduces the problem from
http://src041.bluemosfet.net/treeviewselectsanotheritem.zip
and try it.
or look at these two pieces of code:
<UserControl x:Class="PRJ.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" Loaded="UserControl_Loaded">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel Name="sp1">
<!-- ################################################################### -->
<!-- First, in the TreeView, expand TreeViewItems "a0", "a1" and "a2". -->
<!-- Then, select "a0b0". -->
<!-- Then, click the button twice. -->
<!-- If you don't remove IsTabStop="False" from the following line of code, the TreeView reappears with "a1" selected !!! -->
<!-- If you continue clicking the button, the selection shifts to "a1b0", "a2", and finally "a2b0". -->
<!-- If you remove IsTabStop="False" , or change it to True, the TreeView works OK and keeps its selection. -->
<!-- Isn't it odd, that the capability of the TreeView to keep its selection depends on whether the button used to hide and show the TreeView is a tab stop or not??? -->
<Button Content="Hide/Show TreeView" Name="b1" Click="b1_Click" IsTabStop="False" />
<!-- ################################################################### -->
<sdk:TreeView Name="tv1" />
</StackPanel>
</Grid>
</UserControl>
and
namespace PRJ
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void b1_Click(object sender, RoutedEventArgs e)
{
if (tv1.Visibility==Visibility.Collapsed)
tv1.Visibility=Visibility.Visible;
else
tv1.Visibility=Visibility.Collapsed;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
for (int a=0;a<3;a++)
{
TreeViewItem tvia =new TreeViewItem();
tvia.Header =String.Format("a{0}",a);
for (int b=0;b<2;b++)
{
TreeViewItem tvib =new TreeViewItem();
tvib.Header =String.Format("a{0}b{1}",a,b);
tvia.Items.Add(tvib);
} // for b
tv1.Items.Add(tvia);
} // for a
}
}
}
Isn't it odd that the capability of the TreeView to keep its selection or not depends on whether the button used to hide and show the TreeView is a tab stop or not??

did a test with a brand new project and a treeview fed with a list of strings. Never lost the selected item after collapsing and making visible again.

Related

RegisterPropertyChangedCallback works only the first time

This is a follow up question related to a control that needs to intercept when common control properties changes (Inherit UserControl and hooking up to basic property events).
When any base property changes (foregound, font style, size...), I need to invalidate the canvas and redraw the content.
I've ended up with calling this method inside the UserControl constructor:
RegisterPropertyChangedCallback(DependencyProperty dp, DependencyPropertyChangedCallback callback);
For example:
RegisterPropertyChangedCallback(ForegroundProperty, OnPropertyChanged);
RegisterPropertyChangedCallback(FontFamilyProperty, OnPropertyChanged);
RegisterPropertyChangedCallback(FontSizeProperty, OnPropertyChanged);
Inside OnPropertyChanged I proceed to rewire some property and invalidate the canvas in order to update the picture.
The control is inside a DataTemplate referenced by a Pivot (PivotHeaderTemplate). The default Foreground is the unselected status (semi-transparent SystemControlForegroundBaseMediumBrush) and should get the default "white" when selected (SystemControlHighlightAltBaseHighBrush).
The control Foreground property is applied by a template, and according to the live tree it's updating, but OnPropertyChanges is called only the first time.
For example, the Pivot has 2 views: the first one starts selected and is white, and the second is unselected and is "grayish".
If I change the selection, the view changes, the Foreground property changes accordingly on both controls but OnPropertyChanges is not called, and my canvas doesn't change (because the invalidation logic is inside that method).
[UPDATE: as a workaround, I used the event LayoutUpdated to check what changed.]
For a test project, create a new UWP XAML project a new MyCanvas UserControl.
MyCanvas.xaml:
<UserControl x:Class="InheritControlProperty.Controls.MyCanvas"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:InheritControlProperty.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Canvas x:Name="content" Width="50" Height="50"/>
</UserControl>
MyCanvas.xaml.cs
using System;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
namespace InheritControlProperty.Controls
{
public sealed partial class MyCanvas : UserControl
{
private Color Value
{
get { return (Foreground as SolidColorBrush)?.Color ?? Colors.Black; }
}
public MyCanvas()
{
InitializeComponent();
RegisterPropertyChangedCallback(ForegroundProperty, OnPropertyChanged);
}
private void OnPropertyChanged(DependencyObject sender, DependencyProperty dp)
{
content.Background = new SolidColorBrush(Value);
//Logic for more complex canvas management here
}
}
}
MainPage.xaml
<Page x:Class="InheritControlProperty.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:InheritControlProperty"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cntr="using:InheritControlProperty.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="NameTemplate">
<Grid BorderBrush="Orange" BorderThickness="2">
<cntr:MyCanvas Width="100" Height="50"/>
<!-- Try using Foreground="Red" -->
</Grid>
</DataTemplate>
</Page.Resources>
<Pivot Margin="20" HeaderTemplate="{StaticResource NameTemplate}">
<PivotItem>Item 1</PivotItem>
<PivotItem>Item 2</PivotItem>
</Pivot>
</Page>

What element to use if I only need an placement target element

I want to show a flyout at a specific place. I want to specify a placement target element in XAML, but I want to make sure I am using the "lightest" element possible, given that I don't want that element to ever be visible or interacted with.
Is there a "recommended" or "correct" element to use? If not, what would be the "lightest" element to use? Or am I overthinking this and should just use a button?
I want to show a flyout at a specific place.
The place of FrameworkElements are based on the panel that you are using to hold them. If you want to show your flyout based on a FrameworkElement being placed in a specific place, you can use Canvas to position your FrameworkElement.
I want to specify a placement target element in XAML, but I want to make sure I am using the "lightest" element possible.
Flyout.ShowAt takes FrameworkElement as it's placement target. So I think the "lightest" element would be an empty custom FrameworkElement like below:
public class MyElement:FrameworkElement
{
}
And you can put it into XAML and use Canvas to position it:
<Page
x:Class="PopupSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:PopupSample"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Canvas>
<Button Name="btnClick" Canvas.Left="50" Canvas.Top="500" Click="btnClick_Click">Click Me</Button>
<local:MyElement x:Name="myEle" Canvas.Left="100" Canvas.Top="100"></local:MyElement>
</Canvas>
Code-Behind:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void btnClick_Click(object sender, RoutedEventArgs e)
{
Flyout flyout = new Flyout();
TextBlock tbContent = new TextBlock
{
Text= "this is a flyout content"
};
flyout.Content = tbContent;
flyout.ShowAt(myEle);
}
}
Grid is a pretty light element, it's just a simple Panel-derived class without any child elements. Button is a Control, meaning it has a template which will create many child elements that make up its visual appearance.
Are you saying you want to use a dummy element just for the purpose of specifying the position of the flyout which you will show programmatically? If you want to avoid the element altogether, then maybe a Popup would be a better choice.

What is the difference between null and transparent brush in the Background or Fill

For example we have a Border. What the difference beetween these XAMLs?
1) Background="Transparent"
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Border
BorderBrush="White"
BorderThickness="2"
Width="400"
Height="400"
Background="Transparent"
PointerPressed="Border_PointerPressed"
PointerReleased="Border_PointerReleased" />
</Grid>
2) Background="{x:Null}"
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Border
BorderBrush="White"
BorderThickness="2"
Width="400"
Height="400"
Background="{x:Null}"
PointerPressed="Border_PointerPressed"
PointerReleased="Border_PointerReleased" />
</Grid>
Both of these borders looks identical. But what the difference?
The difference is if we set null background the Border will not support hit-testing, that's why routed events like PonterPressed will not be raised.
Conversely though, if we set Transparent background events will be raised.
To illustrate this let's write code-behind.
using Windows.UI.Xaml.Media;
namespace App1 {
public sealed partial class MainPage : Page {
public MainPage() {
this.InitializeComponent();
}
void Border_PointerPressed(object sender, PointerRoutedEventArgs e) {
Border border = sender as Border;
if (border != null)
border.Background = new SolidColorBrush(Colors.Red);
}
void Border_PointerReleased(object sender, PointerRoutedEventArgs e) {
Border border = sender as Border;
if (border != null)
border.Background = new SolidColorBrush(Colors.Transparent);
}
}
}
1) Let's use the first XAML, compile our app and run it. Try to tap inside the square. The square becomes red because the events are rised and the handlers calls.
2) Now let's use the second XAML, compile the app, run it, tap inside the square. Nothing happens because the events are not rised. The handlers are not calls.
For completeness, I found this link http://msdn.microsoft.com/en-us/library/hh758286.aspx#hit_testing explaining this rather well - see especially the second bullet point:
Hit testing and input events
Determining whether and where in UI an element is visible to mouse,
touch, and stylus input is called hit testing. For touch actions and
also for interaction-specific or manipulation events that are
consequences of a touch action, an element must be hit-test visible in
order to be the event source and fire the event that is associated
with the action. Otherwise, the action passes through the element to
any underlying elements or parent elements in the visual tree that
could interact with that input. There are several factors that affect
hit testing, but you can determine whether a given element can fire
input events by checking its IsHitTestVisible property. This property
returns true only if the element meets these criteria:
The element's Visibility property value is Visible.
The element's Background or Fill property value is not null. A null Brush value results in transparency and hit test invisibility. (To
make an element transparent but also hit testable, use a Transparent
brush instead of null.) Note Background and Fill aren't defined by
UIElement, and are instead defined by different derived classes such
as Control and Shape. But the implications of brushes you use for
foreground and background properties are the same for hit testing and
input events, no matter which subclass implements the properties.
If the element is a control, its IsEnabled property value must be true.
The element must have actual dimensions in layout. An element where either ActualHeight and ActualWidth are 0 won't fire input events.

Let ListView scroll to selected item

I have a WinRT/C#/XAML app with a view that has a vertical ListView of items. Depending on the amount of items the ListView shows a vertical scrollbar. Here's the XAML definition:
<UserControl.Resources>
<CollectionViewSource
x:Name="myViewSource"
Source="{Binding myViewModel.Items}" />
</UserControl.Resources>
...
<ListView
x:Name="myListView"
ItemsSource="{Binding Source={StaticResource myViewSource}}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
</ListView>
Now everytime I navigate to this view, the selected item of the ListView is chosen by setting the databound SelectedItem property in the view model from code behind (OnNavigatedTo). My problem: the ListView doesn't scroll automatically to this selected item. The scrollbar remains at the top of the ListView and the user has to scroll manually to see the selected item.
I tried to execute myListView.ScrollIntoView(MyViewModel.SelectedItem); after setting the SelectedItem in the code behind (in OnNavigatedTo), but it doesn't work. The scrollbar remains at the top.
I'm aware of this thread on SO: Scroll WinRT ListView to particular group .
This seems to be a similar problem. But when I walk the visual tree of the ListView manually or with the WinRT XAML Toolkit, it doesn't find a ScrollViewer (returns null instead).
Thanks to Filip I noticed that calling ScrollIntoView() in OnNavigatedTo() was too early, because the ListView control is not loaded yet in this place.
The first solution idea was to bind the Loaded event of the ListView:
myListView.Loaded += (s, e) =>
myListView.ScrollIntoView(MyViewModel.SelectedItem);
Unfortunately that causes a nasty visual effect, where current ListView items overlap with the selected item for parts of a second, before everything is rearranged well.
The final solution I found is to call ScrollIntoView() asynchronously via the Dispatcher of the view:
myListView.Loaded += (s, e) => Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => myListView.ScrollIntoView(MyViewModel.SelectedItem));
With this solution the layouting works fine.
I had a similar need and resolved it in a slightly different manner. I subscribed to the SelectionChangedEvent from the ListView and performed the scrolling within the handler.
XAML:
<ListView x:Name="myListView" SelectionChanged="myListView_SelectionChanged" ...>
</ListView>
Code:
private void myListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
myListView.ScrollIntoView(myListView.SelectedItem);
}

Auto-complete box under a text box in Windows 8 / Metro UI

I want to implement auto-complete on a textbox in a Windows 8 UI / Metro UI app using C#/XAML.
At the moment, when the soft / touch keyboard shows, it obscures the auto-complete box. However, on the text box focus, Windows 8 automatically scrolls the entire view up and ensures the text box is in focus.
In reality, all I want is the view to scroll up a little more (in fact, by the height of the auto-complete box).
I realise I can intercept the Showing event of InputPane.GetForCurrentView()
I can set InputPaneVisibilityEventArgs.EnsuredFocusedElementInView to true inside the Showing event fine (so Windows won't try to do anything).... however, how can I invoke the same scrolling functionality that Windows 8 would do, but ask it to scroll a little more!?
Here's the code for the main page:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0,200,0,0">
<TextBlock HorizontalAlignment="Center" FontSize="60">App 1</TextBlock>
<TextBlock HorizontalAlignment="Center">Enter text below</TextBlock>
<TextBox HorizontalAlignment="Center" Margin="-10,0,10,0" Width="400" Height="30"/>
<ListBox HorizontalAlignment="Center" Width="400">
<ListBoxItem>Auto complete item 1</ListBoxItem>
<ListBoxItem>Auto complete item 2</ListBoxItem>
<ListBoxItem>Auto complete item 3</ListBoxItem>
<ListBoxItem>Auto complete item 4</ListBoxItem>
<ListBoxItem>Auto complete item 5</ListBoxItem>
</ListBox>
</StackPanel>
</Grid>
If you start up the simulator with the lowest resolution, use the hand to "touch" the textbox, this will bring up the soft keyboard. In the real app, the auto complete list will appear with items as the user enters text.
So in a nutshell, how can I move the screen up a bit more so the user can see the entire autocomplete list?
Bear in mind, in the real app, it'll be worse, as the user may not even notice the autocomplete list appearing "underneath" the keyboard.
I really would appreciate some advice, many thanks!
I have created an AutoCompleteBox for Windows Store apps, the nuget package is available at https://nuget.org/packages/AutoCompleteBoxWinRT
Ok, here is how I would tackle this since I cannot seem to find any way to control the scrolling of the app based on the appearance of the keyboard. I would create a user control that would form the basis for the auto-complete textbox.
<UserControl
x:Class="App6.MyUserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App6"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<TextBox x:Name="textBox" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" GotFocus="textBox_GotFocus" LostFocus="textBox_LostFocus" />
<ListBox x:Name="listBox" Height="150" Margin="0,-150,0,0" VerticalAlignment="Top" Visibility="Collapsed"/>
</Grid>
This is an incredibly basic implementation, so you will have to tweak to meet your needs.
Then, I would add the following code-behind to the user control
public sealed partial class MyUserControl1 : UserControl
{
// Rect occludedRect;
bool hasFocus = false;
public MyUserControl1()
{
this.InitializeComponent();
InputPane.GetForCurrentView().Showing += MyUserControl1_Showing;
}
void MyUserControl1_Showing(InputPane sender, InputPaneVisibilityEventArgs args)
{
if (hasFocus)
{
var occludedRect = args.OccludedRect;
var element = textBox.TransformToVisual(null);
var point = element.TransformPoint(new Point(0, 0));
if (occludedRect.Top < point.Y + textBox.ActualHeight + listBox.ActualHeight)
{
listBox.Margin = new Thickness(0, -listBox.ActualHeight, 0, 0); // Draw above
}
else
{
listBox.Margin = new Thickness(0, textBox.ActualHeight, 0, 0); // draw below
}
}
}
private void textBox_GotFocus(object sender, RoutedEventArgs e)
{
listBox.Visibility = Windows.UI.Xaml.Visibility.Visible;
hasFocus = true;
}
private void textBox_LostFocus(object sender, RoutedEventArgs e)
{
listBox.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
hasFocus = false;
}
}
Next steps would be to expose properties to pass data to be bound to the ListBox. Hard core would be ListBoxItem templating and more, depending on how reusable you wanted it to be.