As the title suggests, I am struggling with retrieving an element from a stackpanel list when tapping it in a simple UWP application. The stackpanel has its itemsource connected to a list of "Customers" which I am then displaying
ObservableCollection<Customer> customers = new ObservableCollection<Customer>();
// Create a new ListView (or GridView) for the UI, add content by setting ItemsSource
ListView customersLV = new ListView();
customersLV.ItemsSource = customers;
// Add the ListView to a parent container in the visual tree (that you created in the corresponding XAML file)
customerPanel.Children.Add(customersLV);
The XAML-code looks like this (Added a scrollviewer for longer lists):
<ScrollViewer VerticalScrollBarVisibility="auto" HorizontalScrollBarVisibility="auto" Margin="63,341,1043,368" >
<StackPanel x:Name="customerPanel" Height="441" Width="394" DoubleTapped="customerPanel_DoubleTapped" ></StackPanel>
</ScrollViewer>
While adding and removing items from the list works great, I cannot seem to access any particular Customer-object from the listpanel when double tapping it.
Here is my doubletap event function:
private void customerPanel_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
testText.Text = e.OriginalSource.ToString();
}
This seems to only print a reference to the whole stackpanel and not to the specific object that I double-tapped. How can I access the tapped Customer-object if I, for example, wanted to call its ToString-method?
Thank you for your time :)
It seems that you just want to get the item value when double click the ListViewItem, so you just need to write a ListView, instead of using ScrollViewer, StackPanel, etc.
So we can write xaml code like:
<Grid>
<ListView x:Name="listView" DoubleTapped="listView_DoubleTapped">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Customer">
<StackPanel Margin="0 10" Orientation="Horizontal">
<Image Width="50" Height="50" Source="{x:Bind Head, Mode=OneWay}"/>
<TextBlock Margin="30 0 0 0" FontSize="25" Text="{x:Bind Name, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Then we define a Customer class.
public class Customer
{
public string Head { get; set; }
public string Name { get; set; }
}
OK, we have done half. Next we are going to create a data collection, and give it to ListView's ItemSource.
public MainPage()
{
this.InitializeComponent();
this.Loaded += MainPage_Loaded;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
ObservableCollection<Customer> list = new ObservableCollection<Customer>();
for (int i = 0; i < 100; i++)
{
Customer p = new Customer()
{
Head = "https://learn.microsoft.com/zh-cn/visualstudio/releases/2019/media/2019_rc_logo.png",
Name = i.ToString()
};
list.Add(p);
}
listView.ItemsSource = list;
}
Final step is to complete the DoubleTapped event.
private void listView_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
var customer = listView.SelectedItem as Customer;
Debug.WriteLine(customer.Name);
}
Done!!!
Solved it now I think!
I simply put a listView within my StackPanel like this.
<ScrollViewer VerticalScrollBarVisibility="auto" HorizontalScrollBarVisibility="auto" Margin="63,341,1043,368" >
<StackPanel x:Name="customerPanel" Height="441" Width="394">
<ListView x:Name="customersLV" Tapped="listView_Tapped">
</ListView>
</StackPanel>
</ScrollViewer>
The CS code looked like this:
ObservableCollection<Customer> customers;
public MainPage()
{
this.InitializeComponent();
customers = new ObservableCollection<Customer>();
customersLV.ItemsSource = customers;
}
private void listView_Tapped(object sender, TappedRoutedEventArgs e)
{
testBox.Text = customersLV.SelectedItem.ToString();
}
This will print out the string representation of the actual Customer object in my testBox textblock :)
Related
I have a WinUI 3 ListView that displays a list of items. Every item has a ToggleSwitch and a Expander. When i click on the ToggleSwitch or the Expander the ListView selection does not change.
I found some solutions for WPF but they dont work in WinUI 3:
Selecting a Textbox Item in a Listbox does not change the selected item of the listbox
How can I do this for WinUI 3 so that the associated ListViewItem is selected when the ToggleSwitch or Expander is selected?
You could handle the Tapped event for the Expander and ToggleSwitch and programmatically set the SelectedItem property of the ListView:
private void OnTapped(object sender, TappedRoutedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
lv.SelectedItem = element.DataContext;
}
XAML:
<ListView x:Name="lv">
<ListView.ItemTemplate>
<DataTemplate>
...
<Expander Tapped="OnTapped" ... />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
If you don't want to change selection when you do something programmatically, you can do it this way.
.xaml
<StackPanel>
<Button
Command="{x:Bind ViewModel.TestCommand}"
Content="Click" />
<ListView
x:Name="ListViewControl"
ItemsSource="{x:Bind ViewModel.Items}"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Item">
<StackPanel>
<ToggleSwitch Toggled="ToggleSwitch_Toggled" />
<Expander Expanding="Expander_Expanding" IsExpanded="{x:Bind IsChecked, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
.xaml.cs
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
if (ViewModel.IsProgrammatical is false)
{
ListViewControl.SelectedItem = (sender as ToggleSwitch)?.DataContext;
}
}
private void Expander_Expanding(Expander sender, ExpanderExpandingEventArgs args)
{
if (ViewModel.IsProgrammatical is false)
{
ListViewControl.SelectedItem = sender.DataContext;
}
}
ViewModel.cs
public partial class Item : ObservableObject
{
[ObservableProperty]
private string text = string.Empty;
[ObservableProperty]
private bool isChecked;
}
public partial class MainWindowViewModel : ObservableObject
{
public bool IsProgrammatical { get; set; }
[ObservableProperty]
private List<Item> items = new()
{
{ new Item() { Text = "A", IsChecked = false,} },
{ new Item() { Text = "B", IsChecked = false,} },
{ new Item() { Text = "C", IsChecked = false,} },
};
[RelayCommand]
private void Test()
{
IsProgrammatical = true;
Items[1].IsChecked = !Items[1].IsChecked;
IsProgrammatical = false;
}
}
Workaround
In this case, the source collection is untouchable and we can't use a flag if the property was changed programmatically or not, we need to use the Tapped event to make the item selected. But unfortunately, the ToggleSwitch's Tapped event is not fired (at least in my environment). Might be a WinUI bug (issue posted here).
As a workaround, at least until this bug gets fixed, you can use the ToggleButton. I tested it out and the Tapped event is fired.
<DataTemplate x:DataType="local:Item">
<StackPanel>
<ToggleButton Tapped="ToggleButton_Tapped" />
<Expander Tapped="Expander_Tapped" IsExpanded="{x:Bind IsChecked, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
private void ToggleButton_Tapped(object sender, TappedRoutedEventArgs e)
{
ListViewControl.SelectedItem = (sender as ToggleSwitch)?.DataContext;
}
private void Expander_Tapped(Expander sender, TappedRoutedEventArgs e)
{
ListViewControl.SelectedItem = sender.DataContext;
}
I want to have a combo box with a button that looks like this:
As I want to use this so that items can be selected and added to a ListView.
Issues:
I don't know how to get and icon in the button like shown
How do you get them to line up really well or is there a way to combine the two elements that I am unaware of?
Here's a working example.
Let's suppose your user control has two controls; a ComboBox and a Button. You want to be able to bind something from your main (parent) to the user control. Then upon selecting something and clicking the button, you want user control to notify to the parent of the event occurrence, and also pass the selected value.
The UserControl XAML:
<UserControl ...
d:DesignHeight="40" d:DesignWidth="200">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="40"/>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0" Margin="4" Name="ItemsComboBox"
ItemsSource="{Binding Source, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<Button Grid.Column="1" Margin="4" Content="+"
Click="Button_Click"/>
</Grid>
</UserControl>
The following binding will allow you to bind a list of data to the combo box, form the parent:
ItemsSource="{Binding Source, RelativeSource={RelativeSource AncestorType=UserControl}}"
From your MainWindow, you'll use the control like so:
<Grid>
<local:UCComboButton Grid.Row="0" Width="200" Height="40" x:Name="MyUC"
Source="{Binding Names}"/>
</Grid>
And in the UserControls code behind:
public partial class UCComboButton : UserControl
{
public UCComboButton()
{
InitializeComponent();
}
// We use this dependency property to bind a list to the combo box.
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(IEnumerable), typeof(UCComboButton), new PropertyMetadata(null));
public IEnumerable Source
{
get { return (IEnumerable)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
// This is to send the occurred event, in this case button click, to the parent, along with the selected data.
public class SelectedItemEventArgs : EventArgs
{
public string SelectedChoice { get; set; }
}
public event EventHandler<SelectedItemEventArgs> ItemHasBeenSelected;
private void Button_Click(object sender, RoutedEventArgs e)
{
var selected = ItemsComboBox.SelectedValue;
ItemHasBeenSelected?.Invoke(this, new SelectedItemEventArgs { SelectedChoice = selected.ToString() });
}
}
Now in the MainWindow.xaml.cs:
public MainWindow()
{
InitializeComponent();
// Subscribe to the item selected event
MyUC.ItemHasBeenSelected += UCButtonClicked;
Names = new List<string>
{
"A",
"B",
"C"
};
DataContext = this;
}
void UCButtonClicked(object sender, UCComboButton.SelectedItemEventArgs e)
{
var value = e.SelectedChoice;
// Do something with the value
}
Note that the above Names list is what's bound to the user control from the main window XAML.
I have been tasked with creating my first UWP App in C#.
The basic idea is to read in an XML file and create objects based on the data read in, then display the properties stored in the object to users in the IU.
Lets say a Person object that has a name, age, and height. I want to display the Person fields after I have read in the data but I can't get anything to show up in the UI after creating the Person object.
I have created a Person class that holds the name, age, height. I have another class that extends ObservableCollection<> and a ItemTemplate that looks for the observable class but currently nothing is showing up on the UI.
Has anyone been through a similar process or know of the correct documentation to read?
Thanks.
First of all in UWP you can choose between two types of binding:
{x:Bind }, is slightly faster at compile time, binds to your Framework Element code-behind class, but it is not as flexible as the other type of binding.
The default mode for this type of binding is OneTime, therefore you will only have your data actually propagated onto your UI, when you construct your object.
{Binding }, in this type of binding where you can only reference variables which exists inside the DataContext of a parent element. The default mode is OneWay.
With that in mind, first of all dealing with a ViewModel which is just a bunch of properties, is different from actually dealing with a Collection, since I don't think the Collection can actually detect alterations on the items itself, but rather on its structure.
Therefore during the Add/Remove process of items in your Collection, you have to actually subscribe/unsubscribe those items to the PropertyChanged EventHandler.
Nevertheless with the following code, i think you should be able to start visualizing updates onto your UI:
VIEWMODEL
public class PersonsObservable<T> : ObservableCollection<Person> where T : INotifyPropertyChanged
{
private PersonsObservable<Person> _personslist;
public PersonsObservable<Person> personslist
{
get { return _personslist; }
set
{
_personslist = value;
_personslist.CollectionChanged += OnObservableCollectionChanged;
}
}
public void OnObservableCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if(e.NewItems != null)
{
foreach (object item in e.NewItems)
((INotifyPropertyChanged)item).PropertyChanged += OnItemPropertyChanged;
}
if(e.OldItems != null)
{
foreach (object item in e.OldItems)
((INotifyPropertyChanged)item).PropertyChanged -= OnItemPropertyChanged;
}
}
public void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((Person)sender));
OnCollectionChanged(args);
}
}
public class Person : INotifyPropertyChanged
{
public Person()
{
_name = "Walter White";
_age = 40;
_height = 180;
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _name;
public string name
{
get
{
return _name;
}
set
{
_name = value;
this.OnPropertyChanged();
}
}
private int _age;
public int age
{
get
{
return _age;
}
set
{
_age = value;
this.OnPropertyChanged();
}
}
private int _height;
public int height
{
get
{
return _height;
}
set
{
_height = value;
this.OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Add Items
PersonsList.Add(new Person());
}
}
XAML
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Orientation="Vertical">
<TextBlock Text="DataBinding" Foreground="DarkBlue" FontSize="18" FontWeight="Bold"/>
<ItemsControl ItemsSource="{Binding Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name: "/>
<TextBlock Text="{Binding name, Mode=TwoWay}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Age: "/>
<TextBlock Text="{Binding age, Mode=TwoWay}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Height: "/>
<TextBlock Text="{Binding height, Mode=TwoWay}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Add Items" Click="Button_Click" Background="Blue" VerticalAlignment="Bottom"/>
</StackPanel>
</Grid>
*Test adding items *
private void Button_Click(object sender, RoutedEventArgs e)
{
// Add Items
PersonsList.Add(new Person());
}
Expose your property and set it to the DataContext of your page (with x:Bind you wouldn't need to do this, but instead you would have to perform a cast for your code to actually compile).
public MainPage()
{
InitializeComponent();
PersonsList = new PersonsObservable<Person>();
this.DataContext = PersonsList;
PersonsList.Add(new Person());
PersonsList.Add(new Person());
}
PersonsObservable<Person> PersonsList { get; set; }
I haven't tested for the situation where one of the items is altered, but you can easily do that, by adding another button (and click event) and actually test if changing one of the items's properties update in your UI.
Anything else, feel free to ask, will be glad to help!
I'm trying to display added elements in a ObservableCollection to show on a ListBox in a Page (MenuPage).
This collection is fed by another page, called AddActivityAdvancedPage. In the AddActivityAdvancedPage, the user fill the form and save the informations that I send it as a object (pmaActivity) to the MenuPage. The MenuPage receive the object and add on the ObservableCollection.
The problem is that my ObservableCollection not hold the the added itens! The itens are not showed on the ListBox.
I debug the code and everytime the application hit the line ListActivitiesAdvanced.Add(pmaActivity); on the MenuPage, the ListActivitiesAdvanced is empty. I need to set the ListActivitiesAdvanced as static in some way, but I don't know how is the right way to do this.
AddActivityAdvancedPage class:
public partial class AddActivityAdvancedPage : PhoneApplicationPage
{
//method called to pass the object pmaActivity as parameter to the MenuPage
private void btnSave_Click(object sender, EventArgs e)
{
Dispatcher.BeginInvoke(() =>
{
PhoneApplicationService.Current.State.Remove("pmaActivity");
PhoneApplicationService.Current.State["pmaActivity"] = pmaActivity;
NavigationService.Navigate(new Uri("/MenuPage.xaml", UriKind.Relative));
});
}
}
MenuPage class:
public partial class MenuPage : PhoneApplicationPage
{
public ObservableCollection<PmaActivity> ListActivitiesAdvanced { get; set; }
public MenuPage()
{
InitializeComponent();
ListActivitiesAdvanced = new ObservableCollection<PmaActivity>();
}
//Method called to receive the pmaActivity and add in the collection
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (PhoneApplicationService.Current.State.ContainsKey("pmaActivity"))
{
PmaActivity pmaActivity = PhoneApplicationService.Current.State["pmaActivity"] as PmaActivity;
PhoneApplicationService.Current.State.Remove("pmaActivity");
ListActivitiesAdvanced.Add(pmaActivity);
}
}
}
ListBox in the MenuPage:
<ListBox ItemsSource="{Binding ListActivitiesAdvanced}" Margin="0,0,12,0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="105" >
<Border BorderThickness="1" Width="73" Height="73" BorderBrush="#FF005DFF" Background="#FF005DFF" Margin="0,10,8,0" VerticalAlignment="Top"/>
<StackPanel Width="370">
<TextBlock Text="{Binding clientName}" TextWrapping="NoWrap"
Margin="12,0,0,0" Style="{StaticResource PhoneTextLargeStyle}"/>
<TextBlock Text="{Binding projectName}" TextWrapping="NoWrap"
Margin="12,-6,0,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I try to remove the ListActivitiesAdvanced from MenuPage and add a x:Name to the ListBox element with the same name: ListActivitiesAdvanced:
<ListBox x:Name="ListActivitiesAdvanced" Margin="0,0,12,0"/>
But in this case, the problem is that this list not hold the previous added itens! Every time I add an item, only the last item added is showed on the ObservableCollection.
Thanks for any help! I'm really have problems with that, there are a lot of ways to bind lists in ListBox (as StaticResource, Source, Binding, List, ObservableCollection, IEnumerable...) and I cannot understand all the differences.
If you want to persist the item list, then why not just put the full list into the application state?
//method called to pass the object pmaActivity as parameter to the MenuPage
private void btnSave_Click(object sender, EventArgs e)
{
Dispatcher.BeginInvoke(() =>
{
List<PmaActivity> activities;
if (PhoneApplicationService.Current.State.ContainsKey("pmaActivities"))
activities = PhoneApplicationService.Current.State["pmaActivities"];
else
activities = new List<PmaActivity>();
activities.Add(pmaActivity);
PhoneApplicationService.Current.State["pmaActivities"] = pmaActivities;
NavigationService.Navigate(new Uri("/MenuPage.xaml", UriKind.Relative));
});
}
Then in the main page, populate from the list:
//Method called to receive the pmaActivity and add in the collection
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (PhoneApplicationService.Current.State.ContainsKey("pmaActivity"))
{
if (PhoneApplicationService.Current.State.ContainsKey("pmaActivities"))
{
var pmaActivities = PhoneApplicationService.Current.State["pmaActivities"] as List<PmaActivity>;
foreach (var activity in pmaActivities)
ListActivitiesAdvanced.Add(activity);
}
}
I'm trying to make a funny comic/meme app like funnyjunk.com, which contains a dislike and a like buttons on every comic, like this : http://s9.postimg.org/ikiyo7iy7/funnyjunk.png
My problem is: I can't get access to code the dislike/like buttons inside the DataTemplate from code behind. Is there any way to access buttons inside DataTemplate?
I'm using ListView in this case, and here is how my DataTemplate looks like :
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Vertical">
<StackPanel>
<Image Source="{Binding Image}" Height="600" Width="800" Stretch="UniformToFill"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Button x:Name="blike" Content="L" FontSize="70" HorizontalAlignment="Center" VerticalAlignment="Center" Click="blike_Click"/>
<TextBlock x:Name="tblrate" Text="0" FontSize="70" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Button x:Name="bdislike" Content="D" FontSize="70" HorizontalAlignment="Center" VerticalAlignment="Center" Click="bdislike_Click"/>
</StackPanel>
</StackPanel>
and Here is the codes for the buttons:
private void blike_Click(object sender, RoutedEventArgs e)
{
int rate = Convert.ToInt16(tblrate.Text);
rate += 1;
tblrate.Text = Convert.ToString(rate);
}
private void bdislike_Click(object sender, RoutedEventArgs e)
{
int rate = Convert.ToInt16(tblrate.Text);
rate -= 1;
tblrate.Text = Convert.ToString(rate);
}
Here's my DataContext (I named it "DataSource"):
public class DataSource
{
public int ID { get; set; }
public string Title { get; set; }
public string Image { get; set; }
public DataSource(int _ID, string _Image, string _Title)
{
ID = _ID;
Image = _Image;
Title = _Title;
}
}
public class DataFill
{
public List<DataSource> Comics= new List<DataSource>();
public void MainPageComics()
{
Comics.Add(new DataSource(1, "/Assets/Comic1.jpg", "Jokur and Botmon"));
Comics.Add(new DataSource(2, "/Assets/Comic2.jpg", "Jokur and Botmon2"));
}
}
What I'm trying to achieve is:
The dislike/like buttons to work in EACH and every comic that is BOUND inside the ListView, so it will change the value of the dislike/like RATE of the comic.
What I have tried:
the 1st answer Jerry Nixon's tutorial (it only works for textbox not textblock, i don't understand why)
I'm still trying to understand to use the Command property, which is very complex.
I'm still looking for tutorials to use ICommands, if you have a tutorial video that would be very helpful.
I'm new to WPF programming, thank you in advance.