How to popup ContextMenu in ListBox programmatically on Windows Phone? - windows-phone-8.1

I have a ListBox in my page, I want some items in the ListBox can popup ContextMenu when you long press it, and some items don't, how can I implement this requriment programmatically?

Here is one possible solution. Define your Item's class with some information about popup:
public class Item
{
public string Info { get; set; }
// Menu attached or not
public bool OptionsEnabled { get; set; }
}
In XAML you will have to define your ListView (or ListBox) with apropriate ItemTelplate:
<ListView Name="myList" Holding="myList_Holding">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Info}" FontSize="24" Margin="7">
<FlyoutBase.AttachedFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="First option"/>
<MenuFlyoutItem Text="Second option"/>
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Then upon Holding event you can check OptionsEnabled property if to show a menu,if yes then do so:
private void myList_Holding(object sender, HoldingRoutedEventArgs e)
{
if (e.OriginalSource == null || !(e.OriginalSource is TextBlock)) return;
TextBlock listItem = e.OriginalSource as TextBlock;
if (listItem.DataContext == null) return;
Item itemData = listItem.DataContext as Item;
if (itemData.OptionsEnabled)
FlyoutBase.ShowAttachedFlyout(listItem);
}
A working sample you can download here.

Related

Instantiate new object from Binding in xaml for Flyout

What I want to achieve may not be possible in XAML. If it is possible then its probably due to a XAML feature worth knowing. If not, then I've also learned something.
I have a button flyout which is data-bound to a view model. The view model provides a new instance of an object to the content of the flyout, via a get accessor.
Each time the button is pressed I want the flyout to present a new instance of the object.
The problem: The object is created only once, and re-presented each time the flyout is opened.
ViewModel.cs
class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
class ViewModel
{
static int itemCount;
public Item GetNewItem {
get {
itemCount++;
Debug.WriteLine("Created item: " + itemCount);
return new Item() { Id = itemCount, Name = "Item_" + itemCount} ;
}
}
}
MainPage.xaml.cs
<Page.Resources>
<local:ViewModel x:Key="ViewModel"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
DataContext="{StaticResource ViewModel}">
<Button Content="Create Item">
<Button.Flyout>
<Flyout>
<StackPanel DataContext="{Binding Path=GetNewItem}">
<TextBlock Text="{Binding Path=Id}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</Grid>
Output:
The trace statement "Created item: Item_1" appears, but not "Created Item_2", etc..
The same data ("1" and "Item_1") is presented each time the button is pressed.
Investigation
I can make it work in the code-behind of the main page. I name the grid, and add an Opening event handler to the flyout
private void Flyout_Opening(object sender, object e) {
var gridDataContext = (ViewModel)this.grid.DataContext;
this.stackPanel.DataContext = gridDataContext.GetNewItem;
}
Works fine now! (but I want to do it in XAML)
I have tried implementing INotifyPropertyChanged on the ViewModel, but this didn't work.
class ViewModel : INotifyPropertyChanged
{
static int itemCount;
public Item GetNewItem {
get {
itemCount++;
Debug.WriteLine("Created item: " + itemCount);
OnPropertyChanged("GetNewItem");
return new Item() { Id = itemCount, Name = "Item_" + itemCount} ;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name) {
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}

Windows phone 8: get value item of listbox

I have a listbox. Every item has an image and title (binding from my list). When I click an item of the listbox, how do I get the title value of that item.
create an event in ListBox called "SelectionChanged" and map it the method in the code behind of the XAML file. In the .cs file get the value from myListBox.SelectedItem and cast it to your list item type.
EX: in XAML file
<ListBox x:Name="myListBox" ItemsSource="{Binding Items}"
SelectionChanged="myListBox_SelectionChanged">
in xaml.cs file:
private void myListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var mySelectedItem = myListBox.SelectedItem as myObject;
}
I hope this helps.
There are already few similar questions (first, second). I'll try to show you an example (little extending this what #KirtiSagar (if it helps accept his solution as it's the same method) has said):
Let's assume that your ItemClass look like this:
public class ItemClass
{
public Uri ImagePath { get; set; }
public string Tile { get; set; } // I used string as an example - it can be any class
}
And you Bind it to your ListBox like this:
<ListBox Name="myList" Grid.Row="2">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImagePath}"/>
<TextBlock Text="{Binding Tile}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It is a very simple example but should show you an overview how it works.
Then in my Page and Constructor I need to add my Collection and subscribe to events:
ObservableCollection<ItemClass> items = new ObservableCollection<ItemClass>();
public MainPage()
{
InitializeComponent();
myList.ItemsSource = items;
myList.SelectionChanged += myList_SelectionChanged;
}
The SelectinChanged event can be used for your purpose in many ways. For example you can use its SelectionChangedEventArgs properties. I will show some methods which will produce the same result. I mixed some things on purpose just to show how it can be done.
private void myList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (myList.SelectedItem != null)
{
string myTile = ((sender as ListBox).SelectedItem as ItemClass).Tile;
// you can also get your item like this - using EventArgs properties:
string myTileToo = ((ItemClass)(e.AddedItems[0])).Tile;
}
// also you can get this by SelectedIndex and your Item Collection
if ((sender as ListBox).SelectedIndex >= 0)
{
string myTileThree = items[myList.SelectedIndex].Tile;
}
}
Note that your LisBox can work in different SelectionModes for example Multipe - you can also try to use that if you need it (there are properties SelectedItems and AddedItems / RemovedItems, which are IList).

Longlist will not update at load

I am having some problems making a long list selector load my data, and i have been unable to find a solution to this problem.
This is my xaml:
<phone:LongListSelector x:Name="animeList"
Margin="0,0,-12,0"
ItemsSource="{Binding Animes}"
Tap="AnimeList_OnTap">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Text="{Binding Name}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu IsZoomEnabled="false">
<toolkit:MenuItem Header="Add as favorit" Click="AddFavorite" />
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</TextBlock>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
My view model is simple:
public ObservableCollection<AnimeItemViewModel> _animes { get; set; }
public ObservableCollection<AnimeItemViewModel> Animes
{
get { return _animes; }
set
{
if (value != _animes)
{
_animes = value;
NotifyPropertyChanged("Animes");
}
}
}
And how i load my data:
public MainPage()
{
InitializeComponent();
DataContext = App.ViewModel;
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
ObservableCollection<AnimeItemViewModel> _animes = new ObservableCollection<AnimeItemViewModel>();
foreach (var i in App.AnimeList.List)
_animes.Add(new AnimeItemViewModel() { AId = i.AId, Name = i.Name });
App.ViewModel.Animes = _animes;
}
And lastly just to show that there are data in the list
Update: I also have a search function, and if i enter a search text will the longlist update, but i am for some reason unable to scroll
private void OnKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
ObservableCollection<AnimeItemViewModel> _animes = new ObservableCollection<AnimeItemViewModel>();
foreach (var i in App.AnimeList.List)
if (string.IsNullOrWhiteSpace(SearchTextBox.Text) || i.Name.ToLower().Contains(SearchTextBox.Text.ToLower()))
_animes.Add(new AnimeItemViewModel() { AId = i.AId, Name = i.Name });
App.ViewModel.Animes = _animes;
}
}
Try placing your loading code in OnNavigatedTo. As for the scrolling issue - setting a proper height to the StackPanel. Let me know if it works.

ContextMenu, get Parent TextBlock Id

ItemSource for the ListBox is a ObservableCollection of Animals.
Each animal have a Name and a Id. Inside the TextBlock showing the name of the animal, I have a ContextMenu. When long tap, it shows a menu UnFollow. When clicking on that Menu it raises the UnFollow_OnClick event.
Now to my question, how can I in my code behind get the Id of the animal?
Have tried some different scenarios but cannot find any working solutions.
<ListBox x:Name="AllAnimals" Margin="0,0,-12,0" ItemsSource="{Binding AllAnimals}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Text="{Binding Name}" TextWrapping="NoWrap" Style="{StaticResource PhoneTextExtraLargeStyle}">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu IsZoomEnabled="False">
<toolkit:MenuItem Header="Unfollow" Click="UnFollow_OnClick" />
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
private void UnFollow_OnClick(object sender, RoutedEventArgs e)
{
}
this should work:
private void UnFollow_OnClick(object sender, RoutedEventArgs e)
{
Animal animal = ((Animal)((sender as FrameworkElement).DataContext));
MenuItem item = (sender as MenuItem);
string itemValue = item.Header.ToString();
if (itemValue == "Unfollow")
{
try
{
if (animal != null)
{
// animal.Id
}
}
catch (Exception)
{
}
}
}
Try this:
private void UnFollow_OnClick(object sender, RoutedEventArgs e)
{
var animal = AllAnimals.SelectedItem as Animal;
if(animal==null) return;
var id = animal.Id;
}
Hope its help.

ICommand will not trigger

My ICommand will not trigger unless I move the DataContext field into the the DataTemplate (contlisttemplate) for the Button. I have images set in a style resource, those disappear as soon as I move the DataContext field into the DataTemplate. Both images and ICommand should be using the same DataContext so I am unsure of why it will not work.
Here is a snippet of my code below.
DataContext="{Binding LongListViewModel, Source={StaticResource viewModelLocator}}"
<i:Interaction.Behaviors>
<GamePad:XboxBehavior StartFocusControlName="continuousList1" IsTopLevelViewForFocus="True"/>
</i:Interaction.Behaviors>
<UserControl.Resources>
<DataTemplate x:Key="contlisttemplate" >
<Button
Command="{Binding Gotodetailpage}"
Style="{StaticResource custherotile}">
</Button>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<xbox:ContinuousList
HorizontalAlignment="Left"
Name="continuousList1"
VerticalAlignment="Top"
ItemTemplate="{StaticResource contlisttemplate}"
ItemsSource="{Binding LongListItems}" Height="316" Width="1280"
Grid.Row="1"
>
<i:Interaction.Behaviors>
<GamePad:XboxBehavior IsContinuousListVuiEnabled="True" HasFocusRetention="True"/>
</i:Interaction.Behaviors>
</xbox:ContinuousList>
public class LongListViewModel : ViewModelBase<LongListViewModel>
{
private readonly IDialogService dialogService;
public Navigateto compass = new Navigateto();
public LongListViewModel()
{
LongListItems = new ObservableCollection<object>();
dictionaryListwithkey = new Dictionary<string, object>();
Gotodetailpage = new RelayCommand(PerformGotoDetailPage);
}
public LongListViewModel(IDialogService dialogService)
: this()
{
this.dialogService = dialogService;
}
public Program getherovideo
{
get { return (Program)LongListItems[0]; }
set
{
//SetProperty(ref currentVideo, value,x => x.CurrentVideo);
}
}
public ObservableCollection<object> LongListItems
{
get;
set;
}
public Dictionary<string, object> dictionaryListwithkey
{
get;
set;
}
public ICommand Gotodetailpage { get; private set; }
private void PerformGotoDetailPage()
{
// Console.WriteLine("List item clicked");
compass.goToDetailsPageWithPath("89");
}
}
In case anyone was wondering what the answer was . As per Aaron Hill ATG :
This looks like an issue of scope. The outer DataContext is your LongListViewModel class, which contains the desired ICommand, but the ItemsSource for the container is set to the LongListItems collection exposed by the view-model. This means the effective DataContext for the DataTemplate is an individual member of the collection, not the overall view-model.
Overriding the DataContext of the DataTemplate would let you point back to the view-model and access the ICommand, however it also means you lose any data present within the individual elements of the LongListItems collection. That is probably why the images no longer work in this case.
Since each item in the collection has its own button, it probably makes sense for the ICommand property to be exposed on the individual item rather than the view-model.