I am currently trying to use WinRT xaml ToolKit TreeView component since it is not supported in UWP natively.
TreeView is inside a button Flyout. When I press the button I want Flyout to appear so I can select an item from the tree. And I bind a command from ViewModel to SelectedItemChanged event:
<Button
x:Name="btnFilter"
HorizontalAlignment="Right"
Command="{Binding OpenFiltersCommand}"
Style="{StaticResource SecondaryMenuButtonStyle}">
<StackPanel Orientation="Horizontal">
<Image
Width="28"
Margin="0,0,4,0"
Source="{StaticResource FilterIcon}" />
<TextBlock x:Uid="Filter" Style="{StaticResource GrayTextBlockStyle}" />
</StackPanel>
<FlyoutBase.AttachedFlyout>
<controls1:CustomFlyout IsOpen="{Binding IsFiltersOpen, Mode=TwoWay}" Parent="{Binding ElementName=btnFilter}">
<controls2:TreeView
ItemContainerStyle="{StaticResource DefaultTreeViewItem}"
ItemTemplate="{StaticResource TreeViewItemTemplate}"
ItemsSource="{Binding BuildingTree}"
Style="{StaticResource DefaultTreeViewStyle}">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SelectedItemChanged">
<core:InvokeCommandAction Command="{Binding ChangeRoomCommand}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</controls2:TreeView>
</controls1:CustomFlyout>
</FlyoutBase.AttachedFlyout>
</Button>
After I select an item from TreeView, SelectedItemChanged event fires as it should, but afterwards I close the Flyout and the event fires again. The second time it usually fires saying new selected element is next after current's parent. So for example if I have this structure:
1
--1.0
--1.1
--1.2
2
--2.0
--2.1
So if I select --1.1 first SelectedItemChanged will fire with --1.1 element, and afterwards it will fire with 2 as new selected item.
Note: I am using CustomFlyout component which I can close from ViewModel, but I also tested this with regular Flyout and same thing happens after I close Flyout by clicking outside of it.
UPDATE: I've downloaded WinRT code and started debugging the TreeView component locally. In TreeViewItem.cs I've found the source of the problem in this function:
protected override void OnGotFocus(RoutedEventArgs e)
{
// Since the GotFocus event will bubble up to the parent
// TreeViewItem (which will make it think it's also selected), it
// needs to ignore that event when it's first been handled by one of
// its nested children. We use the IgnoreNextGotFocus flag to
// notify our parent that GotFocus has already been handled.
TreeViewItem parent = ParentTreeViewItem;
if (parent != null)
{
parent.CancelGotFocusBubble = true;
}
try
{
if (Interaction.AllowGotFocus(e) && !CancelGotFocusBubble)
{
// Select the item when it's focused
Select(true);
// ActivateAsync the selection
IsSelectionActive = true;
UpdateVisualState(true);
Interaction.OnGotFocusBase();
base.OnGotFocus(e);
}
}
finally
{
CancelGotFocusBubble = false;
}
}
From what I can tell when element gets focus sometimes it fails to find it's parent and prevent propagating the event upwards in the tree.
I've also opened an issue on their github repository
As I said previously it seems this is a bug in WinRT TreeView component however I managed to prevent (hard fix) this behavior by binding "IsEnabled" property to IsFiltersOpen property from my ViewModel.
This way when I select my element I close the Flyout and disable TreeView component, which prevents it from updating.
Related
Please let me know if I need to clarify or post more code!
I could find quite some resources on how to pass keystrokes from a child element to a parent element but not the way around.
I explain my use case:
I have a parent Window (let's call it wndSession) with a couple of focusable controls and a ContentControl which gets its content (a UserControl) assigned using DataTemplate bindings.
<Window.Resources>
<DataTemplate DataType="{x:Type vm:vmLearnModule1}">
<local:viewLearnModule1 />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:vmLearnModule2}">
<local:viewLearnModule2 />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:vmLearnModule3}">
<local:viewLearnModule3 />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:vmLearnResults}">
<local:viewLearnResults />
</DataTemplate>
</Window.Resources>
And:
<ContentControl Content = "{Binding learningViewModel}"
Name = "contentControl"
Grid.Row = "1"
Grid.Column = "1"
Height = "300"
Margin = "20, 0, 0, 0" />
Now each UserControl may have different KeyBinginds assigned, and as long as the focus is on the UserControl all works fine.
Example of bindings on a UserControl:
<UserControl.InputBindings>
<KeyBinding Key="F1" Command="{Binding cmdResultNotKnown}"/>
<KeyBinding Key="F2" Command="{Binding cmdResultAlmostKnown}"/>
<KeyBinding Key="F3" Command="{Binding cmdResultKnown}"/>
</UserControl.InputBindings>
My commands are defined all like this sample:
private ICommand _cmdTest;
/// <summary>
/// Test
/// </summary>
public ICommand cmdTest => this._cmdTest ??= new RelayCommand(param => this.doTest(), param => this.canTest);
private bool canTest => true;
private void doTest()
{
// Some code here
}
Now to the issue:
If I click on any focusable control on the parent window wndSession, the UserControl or its currently focused child will lose Focus and a control of the parent (wndSession) will gain focus (ok, I'd expect that so far).
But because of this, the UserControl will not react to KeyBindings anymore unless I click on any control of the UserControl first. That is not a really good UX...
What I need is a way to:
First i thought about something like to "bubble down" the keystroke to the child controls of wndSession, and especially to the UserControl in the Contentcontrol, in the event that wndSession does not already address the KeyBinding.
But as remarked that would cause many issues. So in order to avoid conflicts (which control can now use it's bindings, etc.) I would suppose that what is needed is to pass the keystroke to a specified target, which in my case would be the UserControl inside the ContentControl if wndSession could not handle the key as no binding is specified.
In short: have wndSession operate as a Router passing all unknown keystrokes (not registered by a KeyBinding) to a Gateway/Proxy (ContentControl/UserControl)
Then there will be only a path down the tree with little risk of conflicts. Currently I fail simulating a keystroke on the target control from parent, so that its KeyBinding can fire.
I are facing issue with GridView Control. We had a working Windows Store App on 8.1 where GridView left and right mouse clicks had different functionality. In the case of left mouse click, we used to use “ItemClick” event which performs navigation to another XAML page. On right click of GridItem, it gets selected and shows the appbar, we have used “SelectionChanged” event for this.
We are now migrating our existing windows store app to UWP Application, we have used same gridView Code, we find significant difference in functionality and look & feel, we don’t see GridView Item Selected like above picture. We see “ItemClick” and “SelectionChanged” are working together. The flow is something like that on left click on the item, the control goes to SelectionChanged event and then ItemClick. We were not able to differentiate actions like Left Mouse Click and Right Mouse click, since both events are getting fired up upon clicking on left click/tapping. We have different functionality on left and right clicks of mouse.
Need help on how to mimic windows 8.1 functionality in UWP.
My requirement was the I wanted to use Right Click/Long Tapped to select an item and take an action accordingly from App Bar Buttons and on Left Click/Tap should redirect me to the next XAML Page. The problem I was facing was the on Right Click, I wasnt able to detect that which items of GridView has been clicked and how can I add that into SelectedItem.
What I did was, I introduced extra Grid in DataTemplate of GridView. Within this Grid, I added RightTapped event.
The sample code snippet is
<GridView x:Name="ItemGridView"
ItemsSource="{Binding Source={StaticResource ItemsViewSource}}"
IsItemClickEnabled="True"
SelectionMode="Single" ItemClick="ItemGridView_ItemClick"
SelectionChanged="ItemGridView_SelectionChanged">
<GridView.ItemTemplate>
<DataTemplate>
<Grid RightTapped="Grid_RightTapped">
<Border Background="White" BorderThickness="0" Width="210" Height="85">
<TextBlock Text="{Binding FileName}" />
</Border>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
The event name is Grid_RightTapped. This helped me detect that from which GridViewItem, I got the long tap/right click.
The code-behind for this is:
private void Grid_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
Song selectedItem = (sender as Grid).DataContext as Song;
//the above line will get the exact GridViewItem where the User Clicked
this.ItemGridView.SelectedItem = selectedItem;
//the above line will add the item into SelectedItem and hence, I can take any action after this which I require
}
}
The reason we are doing this way is, because now we can add clicked item into the GridView SelectedItem using Right Click. Now in UWP, clicked items are added into SelectedItem using left click only. And with left click, I can navigate to another page using ItemClick event.
You are correct, there has been a change in the interaction model behavior. According to MSDN article How to change the interaction mode (XAML)
For selection, set IsItemClickEnabled to false and SelectionMode to
any value except ListViewSelectionMode.None and handle the
SelectionChanged event (ItemClick is not raised in this case).
For invoke, set IsItemClickEnabled to true and SelectionMode to
ListViewSelectionMode.None and handle the ItemClick event
(SelectionChanged is not raised in this case).
Another combination is to set IsItemClickEnabled to false and
SelectionMode to ListViewSelectionMode.None. This is the read-only
configuration.
A final configuration, which is used least often, is to set
IsItemClickEnabled to true and SelectionMode to any value except
ListViewSelectionMode.None. In this configuration first ItemClick is
raised and then SelectionChanged is raised.
You seem to be using the last option - IsItemClickEnabled is set to true and SelectionMode is set to something that's not None. According the Microsoft, this is used least often so maybe it would be a good idea to rethink this design?
Since you haven't shared any code that you already tried, I will just throw in one idea: maybe playing around with Tappedand RightTapped event handlers could help you differentiate between the two more easily?
To identify left and right click, for right click you can use RightTapped event
<GridView x:Name="categoryItemsGV"
Margin="5,5,0,0"
IsItemClickEnabled="True"
ItemClick="categoryItemsGV_ItemClick"
IsRightTapEnabled="True"
RightTapped="categoryItemsGV_RightTapped"
SelectionMode="Single"
SizeChanged="categoryItemsGV_SizeChanged"
ItemsSource="{Binding}">
and .cs code is below:
private void categoryItemsGV_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
var tablemod = (sender as GridView).SelectedItem;
}
From RightTapped the item over which the mouse was right clicked can be obtained from e.OriginalSource
<GridView x:Name="myGridView" VerticalAlignment="Center">
<GridView.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="Reset"/>
<MenuFlyoutSeparator/>
<MenuFlyoutItem Text="Repeat"/>
<MenuFlyoutItem Text="Shuffle"/>
</MenuFlyout>
</GridView.ContextFlyout>
</GridView>
Private Sub myGridView_RightTapped(sender As Object, e As RightTappedRoutedEventArgs) Handles myGridView.RightTapped
myGridView.SelectedItem = e.OriginalSource
End Sub
Now that RightClick has selected the desired item, further action like delete, copy can be executed on it.
I have a Pivot control. I want a special behavior -- everytime the user taps the header of currently chosen PivotItem, I want to react to that. However, this reaction can not happen when the tapped header does not belong to the currently selected pivot item.
My plan was as follows:
For each PivotItem create a custom header and associate its tap event with a handler:
<phone:PivotItem DataContext="{Binding}" ContentTemplate="{StaticResource MyTemplate}" Content="{Binding}" x:Name="itemA">
<phone:PivotItem.Header>
<TextBlock x:Name="headerA" Text="A" Tap = "HeaderA_Tapped"/>
</phone:PivotItem.Header>
</phone:PivotItem>
And in the handler, test whether the tapped item is currently selected, if yes, react:
protected void HeaderA_Tapped(object sender, GestureEventArgs e)
{
if (mainPivot.SelectedItem.Equals(itemA))
{
//selected item is the same pivotItem that reported tapping event
react();
}
}
It seemed pretty straightforward, but after giving it a try I found out that the tap event was reported only AFTER the selection changed event. In cases, where the user taps currently not selected pivotItem header, the pivot will change the selection accordingly (default behavior that I want to keep), and only then it reports the tap event. However, that is too late for my code, because in that moment the tapped header and currently selected item are already the same.
Is there any way how I can detect whether the tap initiated a selection change? Or is there a way to revert the order of events? I guess currently the WP event model sinks the event from the root of Visual Tree down to leafs -> therefore the Pivot gets to handle it sooner, and only then it gets to header TextBlock.
I think you should track Pivot SelectedIndex and update SelectedIndex in Selection_Changed event by using a Timer.
Some think like this:
int selectedIndex;
func Selection_ChangedEvent()
{
//use a timer to update selectedIndex
//something like: Timer.do(selectedIndex = Pivot.SelectedIndex, 100)
//fire after a specify time. adjust this for the best result.
}
And in your tapped event
HeaderA_Tapped()
{
if(selectedIndex == "index of item A in Pivot")
{
react();
}
}
Create Pivot selection changed event
<phone:Pivot Title="MY APPLICATION" x:Name="MainPivot" SelectionChanged="Pivot_SelectionChanged">
Add the event handlers to cs file
private void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
switch (MainPivot.SelectedIndex)
{
case 0:
// do something
break;
case 1:
//do something
break;
}
}
For more details see this search
https://www.google.co.in/?gfe_rd=cr&ei=Y1XdVZruLqfG8AfUuquIAQ&gws_rd=ssl#q=wp8+c%23+pivot+selectionchanged
https://social.msdn.microsoft.com/forums/windowsapps/en-US/1baf74fa-0ddd-4226-a02d-a7fc9f80374d/pivot-static-header-like-twitter-app
OK, so after some read of my Windows Phone 8 Development Internals book by Andrew Whitechapel and Sean McKenna (do not ask why I did not do it sooner) I got a working solution.
I am not going into full fledged detailed discussion of events, just to point out conclusions relevant to my issue. It seems that in the WP8 event model there are at least two types of events, lower level routed events and logical touch gesture events. The routed events are routed through visual tree from the most concrete element (in my case the header TextBlock control) to the root (therefore Pivot should have get it later). Logical events are not routed, they appear only on a single element (e.g., my TextBlock control), and therefore their handlers have to be registered on the particular element. I assume that the logical events are raised by the element based on the lower level routed event. Applied to my case, it seems that first the lower-level MouseLeftButtonDown (or button up) event was raised, routed to Pivot that used it to change selection, and only then my TextBlock control created the logical Tap event. This would result in behavior that I observed on my emulator. I am not sure about the way how the routed events are really routed and how the logical events are created, so if I made a mistake in my conclusions, please correct me. However, I was able to solve my problem.
Using aforementioned assumptions I decided to listen to lower-level routed events instead of logical tap gesture. Using this MouseLeftButtonUp event I was notified before the new PivotItem was selected, thus giving me chance to check whether the raised MouseLeftButtonUp was originated by currently selected PivotItem.
And so finally the solution:
My XAML Pivot definition (notice that now I am handling MouseLeftButtonUp events on the TextBlock controls, instead of logical Tap events):
<phone:Pivot Title="Pivot" Name="mainPivot">
<phone:PivotItem DataContext="{Binding A}" ContentTemplate="{StaticResource MyTemplate}" Content="{Binding A}">
<phone:PivotItem.Header>
<TextBlock Text="A" MouseLeftButtonUp="HeaderHandlerA"/>
</phone:PivotItem.Header>
</phone:PivotItem>
<phone:PivotItem DataContext="{Binding B}" ContentTemplate="{StaticResource MyTemplate}" Content="{Binding B}">
<phone:PivotItem.Header>
<TextBlock Text="B" MouseLeftButtonUp="HeaderHandlerB"/>
</phone:PivotItem.Header>
</phone:PivotItem>
</phone:Pivot>
Since I have a different handler for each PivotItem header, I can simply test selected index with a constant. The corresponding handlers in the code-behind:
private void HeaderHandlerA(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// HeaderHandlerA is allways called within the first PivotItem,
// therefore I can just test whether selected index is 0 (index of the first PivotItem)
if (mainPivot.SelectedIndex == 0)
{
OnSelectedItemTapped();
}
// else the "tapped" header was not the selected one and I do nothing
}
private void HeaderHandlerB(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (mainPivot.SelectedIndex == 1)
{
OnSelectedItemTapped();
}
}
Maybe not the most elegant solution, but it works...
I have a ListView that looks like this, that controls which tab in my application that is opened.
<ListView Grid.ColumnSpan="2" Grid.Row="1" SelectedItem="{Binding Path=SelectedSubstanceName}" Name="listView" ItemsSource="{Binding Path=Substances}" HorizontalAlignment="Stretch" Margin="2" VerticalAlignment="Stretch">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Lägg till" Command="{Binding AddSubstanceCommand}"/>
<MenuItem Header="Ta bort" Command="{Binding RemoveSubstanceCommand}"/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I use the SelectedSubstanceName property to detect which tab to open, or switch to, if it's already open.
The property looks like this:
private SubstanceName selectedSubstanceName;
public SubstanceName SelectedSubstanceName
{
get
{
return selectedSubstanceName;
}
set
{
selectedSubstanceName = value;
OnPropertyChanged("SelectedSubstanceName");
if (selectedSubstanceName != null)
{
if (!Tabs.Any(t => t.Identify(selectedSubstanceName.SubstanceNameID, typeof(SubstanceTabsViewModel))))
AddTab(selectedSubstanceName);
else
SelectedTab = Tabs.First(t => t.Identify(selectedSubstanceName.SubstanceNameID, typeof(SubstanceTabsViewModel)));
}
}
}
The case I'm not able to cover is when the user clicks "someSubstance", the corresponding tab is opened, the user closes it, and "someSubstance" is still selected. If the user wants to open it again, he has to select some other substance (which will then be opened), and then click "someSubstance" again. Is it possible to trigger the property even when clicking the same ListViewItem?
I know I could add an event on double-click, but ideally, I want to avoid both events and double-clicks.
I think the problem is that after clicking an item the first time the list's SelectedItem gets set. After clicking the same item the second time SelectedItem won't change because it is already set to that item. What you should do is set the SelectedItem to null after handling the click.
Try to unselect all Items in your ListView after the tab is closed.
YOURLISTVIEW.UnselectAll();
So the next time someone selects an Item there will be a change.
You don't actually want to use the ListView class, but instead simply use the ItemsControl, since it is the most basic way of representing a sequence of elements, but without the extras such as SelectedItem, SelectedValue, etc. that any class deriving from Selector has.
From there, it's merely a matter of how to represent each item in the ItemsControl. The behavior you want is to know when a specific item has been clicked on, which would make the Button class a good candidate, since it handles click behavior through an ICommand interface. Obviously, since you know about DataTemplates and styling in general, you should already know that you can customize how the button looks (visually) without sacrificing the actual behavior (click-handling).
<ItemsControl ItemsSource="{Binding Path=Substances}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource SomeStyleToChangeItsLook}"
Command="{Binding Path=SelectSubstanceCommand}"
CommandParameter="{Binding}"
Content="{Binding Path=Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
public ICommand SelectSubstanceCommand { get; private set; }
private void SelectSubstance(object parameter)
{
// Add the substance that was "clicked" on here however you want to do it.
}
Keep in mind I don't know what framework you are using, so I just gave a general example of how the Command code might look in your view-model. The key to MVVM and using WPFs awesome UI is to always think of what behavior you want and which controls offer that behavior. Ignore how they actually look because that can be changed without losing that behavior.
I have 3 conrtols placed on my Window in WPF, each control contains one or more TextBox and Button, when a new control is selected I would like to change the IsDefault to that button. What I am currently doing is when the TextBox's GotFocus event is fired, I change my Button to IsDefault. This works the first time but when I change from one control to another, and back to the first selected control the second controls IsDefault is still true and when enter is pressed it fires the Click event on the incorrect control.
Is there a way that i can clear all IsDefault properties on my window or what is a better solution to my current way of doing this? See image below for example, when enter is pressed the Button with "..." is fired instead of quick search.
XAML (Update)
<odc:OdcTextBox Text="{Binding AccountNumber, UpdateSourceTrigger=PropertyChanged}" MinWidth="100" Name="txtAccountNumber" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Margin="{Binding TextMargin}">
<odc:OdcTextBox.Buttons>
<Button IsDefault="{Binding ElementName=txtAccountNumber, Path=IsKeyboardFocusWithin}" Width="30" Style="{StaticResource DionysusButton}" x:Name="btnCdv" Content="...">
</Button>
</odc:OdcTextBox.Buttons>
</odc:OdcTextBox>
<Button IsDefault={Binding IsKeyboardFocusWithin, ElementName=ThisGroupBox} />
EDIT:
Your code is slightly different in that you are providing buttons to some custom control. These buttons are declared in a different scope to where they end up (ie I assume they end up inside the OdcTextBox template somewhere). I suspect this is why WPF can't find the named element since that element is named in an outer scope.
You could try using RelativeSource to find the parent OdcTextBox instead:
<Button IsDefault="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type odc:OdcTextBox}}}"
Width="30" Style="{StaticResource DionysusButton}" x:Name="btnCdv" Content="...">
Similarly to how you're using the TextBox GotFocus event, you could do the inverse of this and use the LostFocus event to reset the IsDefault Property