How to reference parent Flyout from Button? [C++/WinRT] - c++-winrt

I have a flyout with a cancel button, where I want the "Cancel" click handler to Hide() the parent flyout. The code segment (below) is part of a ListBoxItem, so I can't reference the flyout by x:name="flyout_name".
This means I need to traverse the Visual Tree Hierarchy, but I'm having trouble accessing the parent Controls::Flyout object.
<Button x:Name="DeleteColButton" Content="Delete">
<Button.Flyout>
<Flyout>
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="ScrollViewer.HorizontalScrollMode" Value="Disabled"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="IsTabStop" Value="True"/>
<Setter Property="TabNavigation" Value="Cycle"/>
</Style>
</Flyout.FlyoutPresenterStyle>
<StackPanel>
<TextBlock TextWrapping="Wrap" Text="Are you sure?"/>
<Button Click="DeleteColClickHandler">Yes</Button>
<Button Click="DeleteColCancelClickHandler">Cancel</Button>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
I can go from the "Cancel" Button to the top-level stackpanel in the flyout, but the next level up is neither Controls::Flyout nor Controls::FlyoutPresenter. I can call panel.GetParent() 11 times after "escaping" the top-level stackpanel, and none of those DependencyObjects show up as Flyout, FlyoutPresenter, StackPanel, ListBoxItem, or ListBox. Is the visual hierarchy of Flyout somehow disconnected from the hierarchy I'm tracing through the XAML?
I've heard that Flyout doesn't have an associated DependencyObject in the visual tree.. Is this really the case? If I try to go top-down, how would I find the DependencyObject of a ListBoxItem?
Should I switch to Dialog?
Edit: I tried to get access to the button for ( Button().Flyout().Hide() ) from the ListBoxItem, but it proved too difficult.
However, using GetOpenPopups() to get the list of open popups - and closing them all - seemed to work. Thank you!

I can go from the "Cancel" Button to the top-level stackpanel in the flyout, but the next level up is neither Controls::Flyout nor Controls::FlyoutPresenter.
For explain this, you need check this document first,
Popups don't exist in the conventional XAML visual tree that begins from the root visual, although they are associated with the app main window. Unless your app maintains a list of all the Popup elements you've created as well as a status (the IsOpen value), it can be difficult to keep track of them. Sometimes you will want to clear all popups prior to initiating another UI action, like navigating the page or displaying a flyout.
Derive from above the flyout will render on the pop's content. so you could use GetOpenPopups method to get it, if you want to hidden it just set the found Popup isopen property as false like the following.
private void DeleteColCancelClickHandler(object sender, RoutedEventArgs e)
{
var popups = VisualTreeHelper.GetOpenPopups(Window.Current);
foreach (var popup in popups)
{
if (popup is Popup)
{
popup.IsOpen = false;
}
}
}
And the other way is get flyout with Button Flyout Property
var flyout = DeleteColButton.Flyout;
flyout.Hide();
C++/WinRT
auto popups = VisualTreeHelper::GetOpenPopups(Window::Current());
for (int i = 0; i < popups.Size(); i++)
{
auto popup = popups.GetAt(i);
auto name = get_class_name(popup);
if (get_class_name(popup) == L"Windows.UI.Xaml.Controls.Primitives.Popup")
{
popup.IsOpen(false);
}
}

Related

SDK AutoCompleteBox Ignores IsTabStop odd issue

I've got a few sdk AutoCompleteBox's that I just want to set IsTabStop="False" on. If you do so it's ignored and still takes focus/tab action.
So I went digging into the template and found the embedded TextBox in there with it explicitly hardcoded to IsTabStop=True so I figured I could just pull that out, TemplateBind to an unused property like Tag to set it at the instance level and just provide a setter in the template for default True, right?
No joy though, it still ignores it. So I tried just setting it False explicitly in the template. Which sure enough removes it from the Tab order. HOWEVER, it also disables the control so it won't accept focus or edit at all.
I've ran into similar situations in the past but none of my tricks are working. Anyone ran into this before? What am I missing to just make the damn thing fall out of tab order? I'm also rather confused why setting the IsTabStop on the embedded TextBox would disable the control from all hittestvisibility....
Not sure where the reasoning is for setting to just have the property ignored nor have found explanation in any docs yet.
So, maybe another pair of eyes might help before I go too far down the rabbit hole for something seemingly so simple, any thoughts? Thanks!
The TextBox inside the AutoCompleteBox can only own keyboard focus when all criteria for focus ownership are met:
must be derived from Control
must be enabled
must be visible
must be loaded (how to test: Loaded event fired / visual parent in not null anymore [I use an extension method IsLoaded])
and... IsTabStop must be true
Therefore you have to let the internal TextBox have its tabstoppy ways.
But we have other options.
I thought about using a ContentControl and setting TabNavigation="Once". As far as I understood it is supposed to have the Control and all its content behave like one solid piece in the tab navigation chain, so theoretically when tabbing you will step at the ContentControl, but never down into its content.
But I tried it and it is not working as expected. Don't know where my thinking is wrong here.
I played around with it and found the following solution is working:
Surround them with a FocusDitcher (we derive from ContentControl und make the control ditch the focus OnGotFocus, but not if triggered by the user clicking into the inner TextBox). We could alternatively write a Behavior for that. For focus ditching, the control has to sabotage one of the prerequisites for focus ownership, like going to disabled mode and back to enabled (not working with IsTabStop, tried it, failed).
// helper control
public class FocusCatcher : Control { }
[TemplatePart( Name = "ReversedTabSequenceEntryPointElement", Type = typeof( UIElement ) )]
public class FocusDitcher : ContentControl
{
public FocusDitcher()
{
DefaultStyleKey = typeof( FocusDitcher );
TabNavigation = KeyboardNavigationMode.Once;
}
private UIElement m_reversedTabSequenceEntryPoint;
protected override void OnGotFocus( RoutedEventArgs e )
{
if (FocusManager.GetFocusedElement() == this)
{
DitchFocus();
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
m_reversedTabSequenceEntryPoint = (UIElement) GetTemplateChild( "ReversedTabSequenceEntryPointElement" );
m_reversedTabSequenceEntryPoint.GotFocus += OnReversedTabSequenceEntryPointGotFocus;
}
private void OnReversedTabSequenceEntryPointGotFocus( object sender, RoutedEventArgs e )
{
// tweak tab index to ensure that when we ditch the focus it will go to the first one in the tab order
// otherwise it would not be possible to ever shift+tab back to any element precceeding this one in the tab order
var localTabIndexValue = ReadLocalValue(TabIndexProperty);
TabIndex = Int32.MinValue;
DitchFocus();
if (DependencyProperty.UnsetValue == localTabIndexValue)
ClearValue( TabIndexProperty );
else
TabIndex = (int) localTabIndexValue;
}
private void DitchFocus()
{
IsEnabled = false; // now we lose the focus and it should go to the next one in the tab order
IsEnabled = true; // set the trap for next time
}
}
and template
<Style TargetType="myApp:FocusDitcher">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="myApp:FocusDitcher">
<Grid>
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Cursor="{TemplateBinding Cursor}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<myApp:FocusCatcher x:Name="ReversedTabSequenceEntryPointElement"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

UWP - Close my content dialog on click on the blank area

I have created a content dialog for my UWP app which involves a centralized UI Element and a surrounding blank area.But content dialog does not have a property like "IsLightDismissEnabled" to close the dialog on click on an area except the UIELEMENT area.How can I achieve it?
In the code behind your content dialog:
public sealed partial class CustomDialog : ContentDialog
{
public CustomDialog()
{
this.InitializeComponent();
Boolean isHide;
Window.Current.CoreWindow.PointerPressed += (s, e) =>
{
if (isHide)
Hide();
};
PointerExited += (s, e) => isHide = true;
PointerEntered += (s, e) => isHide = false;
}
}
There are few options that I can think of:
Use popup (like uruk suggested) and add your controls inside, create the popup at desired location (you could also use binding here if you want to show the popup at location depending on user input at runtime Popup has HorizontalOffset and VerticalOffset properties)
Create a parent view that is taking up the whole page but is transparent, then add your UI elements at the center and attach tap/click events to the transparent view. These events are going to just close remove/collapse the transparent view which contains the other elements inside (Either by binding of values or by setting the values to the UI elements).
Example or popup usage:
<Popup x:Name="MenuPopUp"
IsLightDismissEnabled="True"
HorizontalOffset="{Binding HorizontalOffset}"
VerticalOffset="{Binding VerticalOffset}"
IsOpen="{Binding IsOpen, Mode=TwoWay}">
<Grid>
YOUR ELEMENTS HERE
</Grid>
</Popup>
Content dialog is a modal dialog. Why don't you use a Popup or a child class of it? It's non-modal, and it already has the IsLightDismissEnabled property you just mentioned.
<Popup x:Name="MenuPopUp"
IsLightDismissEnabled="True"
LostFocus="MenuPopUp_LostFocus"/>
In CS
private void MenuPopUp_LostFocus(object sender, RoutedEventArgs e)
{
MenuPopup.IsOpen = false;
}

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.

Sharing styles between several GridView controls

I need to style several GridView throughout my application with the same visual styles. This style includes customizing the ItemsPanel property as well as the GroupStyle property.
My problem is that the GroupStyle property of GridView is not a dependency property. So the code I would have liked to write (see below) does not work.
Do you know a clean way to share a style (including GroupStyle) between several GridViews?
The only thing I can think of is using a GroupStyleSelector but it's kind of stupid since there is no selection to make: it's always the same GroupStyle that's being used. Moreover, I suspect it wouldn't be reflected at design time in VS & Blend.
The code I would love to use:
<GridView
ItemsSource="..."
ItemTemplate="..."
Style="{StaticResource MainMenuStyle}"/>
<Style TargetType="GridView" x:Key="MainMenuStyle">
<Setter Property="ItemsPanel">
<Setter.Value>
...
</Setter.Value>
</Setter>
<Setter Property="GroupStyle">
<Setter.Value>
<GroupStyle>
...
</GroupStyle>
</Setter.Value>
</Setter>
</Style>
I've got a magical happy solution.
You can create a custom Attached Property that you set in the Style, and upon setting it internally sets the GroupStyle property on the GridView.
Attached Property:
// Workaround for lack of generics in XAML
public class GroupStyleCollection : Collection<GroupStyle>
{
}
public class GroupStyleHelper
{
public static ICollection<GroupStyle> GetGroupStyle(ItemsControl obj)
{
return (ICollection<GroupStyle>)obj.GetValue(GroupStyleProperty);
}
public static void SetGroupStyle(ItemsControl obj, ICollection<GroupStyle> value)
{
obj.SetValue(GroupStyleProperty, value);
}
public static readonly DependencyProperty GroupStyleProperty =
DependencyProperty.RegisterAttached(
"GroupStyle",
typeof(ICollection<GroupStyle>),
typeof(GroupStyleHelper),
new PropertyMetadata(null, OnGroupStyleChanged));
private static void OnGroupStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ItemsControl itemsControl = d as ItemsControl;
if (itemsControl == null)
return;
RefreshGroupStyle(itemsControl, GetGroupStyle(itemsControl));
}
private static void RefreshGroupStyle(ItemsControl itemsControl, IEnumerable<GroupStyle> groupStyle)
{
itemsControl.GroupStyle.Clear();
if (groupStyle == null)
return;
foreach (var item in groupStyle)
{
itemsControl.GroupStyle.Add(item);
}
}
}
XAML Style:
<Style TargetType="ItemsControl">
<Setter Property="GroupStyleTest:GroupStyleHelper.GroupStyle">
<Setter.Value>
<GroupStyleTest:GroupStyleCollection>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock FontWeight="Bold" FontSize="15" Text="{Binding Path=Name}" Foreground="HotPink"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</GroupStyleTest:GroupStyleCollection>
</Setter.Value>
</Setter>
</Style>
Disclaimer: I'm testing this in WPF rather than WinRT but it should work the same, as far as I can tell. That's also why I'm using an ItemsControl rather than GridView, but the property is ItemsControl.GroupStyle anyway.
I've a solution and that will definitely work as per your question, but though you should decide whether to use that in your case or not.
If you have to make same style of a control in all over the project, then you should make one common folder and in that folder
create one "Custom User Control" and apply all of your style and
customize it however you want.
After that when you need to apply that same kind of style on same control (any grid control) then simply add that customized user
control instead of predefined control
By doing this you'll also achieve MVC architecture and modularity.
I'm developing Windows 8 Metro app in C# with XAML, and in that whenever i wanted this approach then i always use this solution and it always works...
to create custom user control, you should use visual studio & in that right click on project -> add -> new item -> User Control
(Sorry if you couldn't find your solution here, but i think this might help...)

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);
}