I am making a library management software. This is the XAML:
<CommandBar Grid.Row="0">
<AppBarButton Icon="Add"
Label="New Book"
Click="NewBook_Click"/>
<AppBarButton Icon="Delete"
Label="Remove a Book"
Click="DeleteBook_Click"/>
</CommandBar>
<GridView x:Name="AllBooks_GridView"
ItemsSource="{x:Bind Path=ViewModel.Books, Mode=OneWay}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollMode="Enabled"
ScrollViewer.HorizontalScrollMode="Disabled"
Grid.Row="1"
SelectionMode="Multiple">
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:Book">
<StackPanel Margin="10" HorizontalAlignment="Center">
<Image Width="200" Height="200" Source="{x:Bind Path=CoverImageLocation, Mode=OneWay}" />
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock FontSize="16"
Text="{x:Bind Path=Title, Mode=OneWay}"
TextTrimming="WordEllipsis"/>
<TextBlock FontSize="10"
Text="{x:Bind Path=Author, Mode=OneWay}"
Margin="0, 3, 0, 0"
TextTrimming="WordEllipsis"/>
</StackPanel>
<TextBlock FontSize="20"
Text="{x:Bind Path=Quantity, Mode=OneWay}"
Grid.Column="1"
VerticalAlignment="Bottom"
Margin="20, 0, 0, 0"
HorizontalAlignment="Left"/>
</Grid>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
I want to select multiple bar and delete them when the second app bar button is pressed. This is the DeleteBook_Click function:
private void DeleteBook_Click(object sender, RoutedEventArgs e)
{
var books = AllBooks_GridView.SelectedItems;
foreach (var b in books)
{
var book = b as Book;
DataAccess.DeleteBook(book.Title);
ViewModel.Books.Remove(book);
}
}
This is the screenshot of my app:
When I click the delete icon, only the first book is deleted. Although both of them are selected. What am I doing wrong?
You are changing the collection while you iterating through its items.
AllBooks_GridView.SelectedItems is the collection, and when you are iterating through it in the foreach loop, you change it by calling
ViewModel.Books.Remove(book);
After this call, the GridView is refreshed to reflect the change you make to the ViewModel, and the items in the SelectedItems collection becomes less. And in the next iteration, the SelectedItems no longer contains two books as you may have expected, it contains only one. And since the code is trying to remove the 2nd book from the collection in the next iteration, so nothing happens and the loop terminates.
Here is a fix:
//Now the books collection is no longer bound to the SelecteItems of the GridView,
//it is 'immutable' in the foreach loop
List<Book> books = new List<Book>();
foreach (var item in AllBooks_GridView.SelectedItems)
books.Add(item as Book);
foreach (var book in books)
{
DataAccess.DeleteBook(book.Title);
ViewModel.Books.Remove(book);
}
When you remove a book from the Books collection, ObservableCollection will notify the data binding about the change which will, in turn, modify the SelectedItems collection.
Usually, this throws an exception but in this case, doesn't as it gets "masked" by the fact that removing the first item just moves the second item in its place (lowering the size of the collection) and enumerating forward just checks if the next index exists, and because it does not, the loop doesn't continue. You can confirm this by selecting three items - the first one will get deleted, the second one will get skipped, but third will be deleted again.
The simplest solution is to make sure to iterate over a different collection than the SelectedItems property. The cheapest way to achieve that is to use the LINQ ToArray extension on the first line.
var books = AllBooks_GridView.SelectedItems.ToArray();
This will create a new array which contains all the books that were selected and when you then remove from ViewModel.Books and it updates SelectedItems, it will no longer matter, as our books variable is a different instance.
The reason is given in Kennyzx's answer.
To make it work correctly, do something like this:
private void DeleteBook_Click(object sender, RoutedEventArgs e)
{
var books = AllBooks_GridView.SelectedItems.ToList(); // this will create a new list
foreach (var b in books)
{
var book = b as Book;
DataAccess.DeleteBook(book.Title);
ViewModel.Books.Remove(book);
}
}
Related
When a node in TreeView contains many elements, like above 2000, its expanding and collapsing is very slow. For ListView I was using incremental loading:
<ListView
Width="500"
MaxHeight="400"
IsItemClickEnabled = "False"
SelectionMode ="None"
IncrementalLoadingThreshold="5"
IncrementalLoadingTrigger="Edge"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollMode="Enabled"/>
However I do not see such option for TreeView. How this can be optimized?
Slow expanding and collapsing TreeView nodes
Both TreeView and TreeViewItem do not contain IncrementalLoading behavior, so you can't make increment loading for treeview. But you could porcess data soure with linq select-take method to implement Incremental loading function.
placing a button in the last TreeViewItem that used to load more items.
For example
<DataTemplate x:Key="FileTemplate" x:DataType="local:ExplorerItem">
<TreeViewItem AutomationProperties.Name="{x:Bind Name}" IsSelected="{x:Bind IsSelected,Mode=TwoWay}">
<StackPanel Orientation="Horizontal">
<Image Width="20" Source="../Assets/file.png" />
<TextBlock Margin="0,0,10,0" />
<TextBlock Text="{x:Bind Name}" />
<Button Background="White" Margin="15,0,0,0" x:Name="LoadMore" Visibility="{Binding IsLastItem}"
Command="{Binding LoadMoreCommand}" CommandParameter="{Binding}">
<SymbolIcon Symbol="More"/>
</Button>
</StackPanel>
</TreeViewItem>
</DataTemplate>
Code Behind
WeakReferenceMessenger.Default.Register<object>(this, (s, e) =>
{
var temp = subitems.Skip(subitems.IndexOf(e as ExplorerItem)+1).Take(20);
(e as ExplorerItem).IsLastItem = false;
foreach (var item in temp)
{
folder1.Children.Add(item);
}
folder1.Children.Last().IsLastItem = true;
});
For complete code, please refer to this link.
TLDR
CollectionView broken in iOS, cant find any solution online. Weak Google fu or just not seeing the answer.
The problem
I have a CollectionView (Lets call it A) that contains items that are only strings values for filtering on a different CollectionView (Lets call it B) containing results from selecting a filter option in CollectionView A.
I have 3 different filtering options: "All", "Close to you", "Online" in CollectionView A.
The problem is that on iOS when I select for example filter option "All" in CollectionView A for the first time on the page it does the filtering and shows the results in CollectionView B, when I choose for example filtering option "Online" in CollectionView A it filters and shows the results in CollectionView B.
But when I choose filtering option "All" for the second time in CollectionView A its not responding, no event triggers, no commands runs and its not disabled. It is only showing, but I cant do anything with it.
Expected result
Can reselect the previous item on iOS, in Android no problem.
Actual result
Cant reselect previous item on iOS, need to back to previous page in stack and then navigate back to page to reset filtering.
The xaml markup
This is the xaml markup for the CollectionView A as explained above, the only holding string values to filter on.
<CollectionView ItemsSource="{Binding FilterLocations}"
Grid.Row="2"
SelectedItem="{Binding SelectedFilterLocation}"
SelectionMode="Single"
HeightRequest="50">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
ItemSpacing="10" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<StackLayout xct:TouchEffect.NativeAnimation="True">
<Frame BorderColor="{StaticResource BorderColor}"
x:Name="subCategoryFrame"
Padding="14, 10">
<Label Text="{Binding .}"
x:Name="subCategoryName"
FontFamily="{StaticResource FontPoppinsLight}"
TextColor="{StaticResource PrimaryAlt}" />
</Frame>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.Header>
<BoxView WidthRequest="0"
HeightRequest="1"
BackgroundColor="Transparent" />
</CollectionView.Header>
<CollectionView.Footer>
<BoxView WidthRequest="{StaticResource NormalSpacingDouble}"
HeightRequest="1"
BackgroundColor="Transparent" />
</CollectionView.Footer>
</CollectionView>
The CollectionView A is in SelectionMode:Single, and the SelectedItem is bound to a ICommand on its bound ViewModel. And in the ViewModel the selection of a item in CollectionView A will trigger a filtering in CollectionView B
What I done so far
I set up so if a item in the CollectionView A is disabled it become Red, but it dont become Red.
I have tried to add a event in the Code behind, to try and set SelectedItem to null, but that back fired and just made all events go twice, one for selecting the item on the screen and second for altering the SelectedItem in the code behind.
Code:
private void CollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (Device.RuntimePlatform == Device.iOS)
{
var collectionView = sender as CollectionView;
if (collectionView == null) return;
if (collectionView.SelectedItem != null)
{
collectionView.SelectedItem = null;
}
}
}
(I know it is a big no no to do logic stuff in the Code Behind that is not design logic, but I need to get this solved or have a quick and dirty fix because of time pressure.)
Sorry for the wall of text
Use a TapGestureRecognizer.
Below, MyItemCommand is defined in MyViewModel and {Binding .} refers to the item selected in ItemsSource.
<DataTemplate ...>
<StackLayout>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MyViewModel}},
Path=MyItemCommand}"
CommandParameter="{Binding .}"/>
</StackLayout.GestureRecognizers>
...
First Bind the collection view into the event selectionchange.
<CollectionView ItemsSource="{Binding FilterLocations}"
Grid.Row="2"
SelectedItem="{Binding SelectedFilterLocation}"
SelectionMode="Single"
HeightRequest="50"
SelectionChanged="collection_SelectionChanged">
then inside the event put selected item to null.
private async void collection_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
var Sender = sender as CollectionView;
if (Sender.SelectedItem == null)
return;
Sender.SelectedItem = null;
}
catch (Exception ex)
{
}
}
I am having a strange problem in my UWP application.
I have a ListView bound to an ObservableCollection<SomeInterface>. I am changing one property of all the items in the ObservableCollection. The problem is that because of the ObservableCollection does not take a class as the type argument, rather it takes an interface, I cannot figure out how to implement INotifyCollectionChanged on the interface.
So I went ahead and changed the property of all items in the collection ( without implementing INotifyPropertyChanged ). This is giving weird behavior. When I change the property ( I do this as soon as I navigate to the page containing the ListView ) It only affects only those ListView items that are not in view currently ie, if there are 100 items in the ListView and I can see the first 10 without scrolling down then those 10 items are unchanged but when I scroll down, I see that other (other than the first 10) ListViewItems are reflecting the changes that were made. And to add to this, when I scroll up again ( to the first 10 items ), I see that now they are also changed.
to summarize, only the items that are not currently in the view get updated.
here's my code to update the ObservableCollection:
class SomePage : Page
{
private ObservabeCollection<SomeInterface> SomeObservableCollection { get; set; } = new ObservabeCollection<SomeInterface>();
...
private async Task ModifyObservableCollection()
{
var response = await MakeApiCall();
var facets = response.ListOfItems;
foreach (var item in SomeObservableCollection.ToList())
{
var fromApi = facets.author.FirstOrDefault(i => i.key == item.key);
if (fromApi == null) continue;
var itemInList = SomeList.FirstOrDefault(i => i.key == fromApi.key);
itemInList.read = fromApi.read;
itemInList.num = fromApi.num;
//here!!
itemInList = SomeObservableCollection.FirstOrDefault(i => i.key == fromApi.key);
itemInList.read = fromApi.read;
itemInList.num = fromApi.num;
}
}
}
This is my ListView:
<ListView ItemsSource="{x:Bind AuthorFacets, Mode=OneWay}"
ItemTemplate="{StaticResource ListViewDataTemplate}"
SelectionMode="Multiple">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
My itemtemplate:
<DataTemplate x:Key="ListViewDataTemplate"
x:DataType="local:ISomeInterface">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind read}"/>
<TextBlock Text="{x:Bind num}"/>
</Grid>
</DataTemplate>
Change
<TextBlock Text="{x:Bind read}"/>
<TextBlock Text="{x:Bind num}"/>
to
<TextBlock Text="{x:Bind read, Mode=OneWay}"/>
<TextBlock Text="{x:Bind num, Mode=OneWay}"/>
And implement INotifyPropertyChanged, and it should work. By default, x:Bind is OneTime unlike traditional binding (because performance / memory) so your items in view won't update.
Without INPC? You could possibly try calling Bindings.Update() on the parent view after changing the properties, though you may lose scroll position.
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 :-) .
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.