I populate my listview like this from my sqlite database
private async void getBowlers()
{
SQLiteAsyncConnection conn = new SQLiteAsyncConnection(BOWLERS_DATABASE);
var query = conn.Table<Bowler>();
var result = await query.ToListAsync();
List<String> names = new List<String>();
foreach (var item in result)
{
names.Add(item.Name);
}
itemListView.ItemsSource = names;
}
when I click on one of the items in the list, how can I get the data associated with the clicked item?
since I am just populating the list with a list of strings I really dont see how I can associate any data with it so is there another way to populate my listview? Even just getting the ID would be fine then I could just query based on the ID
You can bind your ListView to an object, rather than just a string. If you do so, when an item is clicked, you will get back the object. In your case, you can bind to the Bowler object, which I assume has an ID and Name field:
public class Bowler
{
public int Id { get; set; }
public string Name { get; set; }
}
In your XAML, you have to tell the ListView that you want to bind the Name field. Here is a simple ListView that just has a TextBlock for each item:
<ListView x:Name="itemListView" ItemsSource="{Binding}"
IsItemClickEnabled="True" ItemClick="itemListView_ItemClick">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the above, the TextBlock will display the Name field.
To load items into the list, you could change your above method to look something like:
// Unrelated, but generally you want to use "async Task"
// in the method signature. "async void" should only be used by
// event handlers, such as a click event.
private async Task getBowlers()
{
SQLiteAsyncConnection conn = new SQLiteAsyncConnection(BOWLERS_DATABASE);
var query = conn.Table<Bowler>();
var result = await query.ToListAsync();
// set page's data context to bowler collection
this.DataContext = result;
}
Now when an item is clicked, you will get back the related Bowler object:
private void itemListView_ItemClick(object sender, ItemClickEventArgs e)
{
Bowler bowler = (Bowler)e.ClickedItem;
// do something with bowler...
}
More info: Windows 8 Metro App ListView Binding and Editing
Related
This's my first question here, so hi everybody.
I'm working on the mobile app in Xamarin.Forms with Prism. I've created ListView where shown data from the database.
When the user clicks in the selected row app should navigate to a new view and pass the selected item from ListView.
<ListView x:Name="DefectsBase"
RowHeight="65"
ItemsSource="{Binding Defects}"
ItemSelected="ShowDetailsEvent"
IsPullToRefreshEnabled="true"
RefreshCommand="{Binding Refresh}"
IsRefreshing="{Binding IsRefreshing}">
Code backend:
async void ShowDetailsEvent(object sender, EventArgs e)
{
var myListView = (ListView)sender;
var myItem = myListView.SelectedItem;
var p = new NavigationParameters();
p.Add("selectedDefect", myItem);
await _navigationService.NavigateAsync("DefectDetailsView", p);
}
Unfortunately, the app doesn't respond to pressing the selected row in ListView.
As I can see you are already using Prism and you have a List page with Items and you want to navigate to some details page based on the selected/taped/chosen item which the user taps in the ListView.
The idea is to move as much code and logic as we can to the view model and keep our code-behind. This is pretty easy to solve using Prism and EventToCommand behaviour.
In the example and answer below, I will show you how to solve this with few lines of code, with a nice code approach.
First of all, I recommend you use EventToCommand behaviour, you can include it with prism xmlns, like this: xmlns:prism="http://prismlibrary.com", later on, you can use it with ListView.
Remove ItemSelected event from your ListView and move the markup about it to the <ListView.Behaviors> part. Here is my code sample for the ListView which binds to some ObserverableCollection of the Car models:
<ListView ItemsSource="{Binding Cars}">
<ListView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<prism:EventToCommandBehavior EventName="ItemTapped"
Command="{Binding SelectedCarCommand}"
EventArgsParameterPath="Item" />
</ListView.Behaviors>
The main part here is <ListView.Behaviors>, where you can see that I am binding to the SelectedCarCommand which will be invoked when the user taps on some of the items from the list. I am using the ItemTapped event for this and passing the current "taped" item from the list as a parameter.
In order to follow this XAML part in my view model of this page, I have declared the DelegateCommand and method which will be called when the command is invoked. The view model part looks like this:
This is my CarListPageViewModel, take a look at DelegateCommand and SelectedCar method.
public class CarListPageViewModel
{
private readonly INavigationService _navigationService;
public ObservableCollection<Car> Cars { get; set; }
public DelegateCommand<Car> SelectedCarCommand { get; private set; }
public CarListPageViewModel(INavigationService navigationService, IDataProvider dataProvider)
{
_navigationService = navigationService;
// Insert test data into collection of Cars
Cars = new ObservableCollection<Car>(dataProvider.GetData());
SelectedCarCommand = new DelegateCommand<Car>(SelectedCar);
}
private async void SelectedCar(Car selectedCar)
{
NavigationParameters navigationParameters = new NavigationParameters
{
{ "selectedCar", selectedCar }
};
await _navigationService.NavigateAsync(nameof(CarDetailsPage), navigationParameters);
}
}
As you can see we have DelegateCommand defined with the type of parameter which will be passed, in my case, this is the Car class, the same class as our items in the ListView.
In the constructor, I did my initialization and defined the method which will be called, that method has a parameter of the type Car.
When the user taps on one of the items in the ListView, SelectedCar (method) will be called and we can pass the data to the next view using NavigationParameters and NavigationService.
In order to retrieve the passed data we can use INavigationAware in the details view model and with the OnNavigatedTo method, access the data which is being passed.
This is my CarDetailsPageViewModel, take a look at OnNavigatedTo method.
public class CarDetailsPageViewModel : BindableBase, INavigationAware
{
private string carTitle;
public string CarTitle
{
get { return carTitle; }
set { SetProperty(ref carTitle, value); }
}
private string photoUrl;
public string PhotoUrl
{
get { return photoUrl; }
set { SetProperty(ref photoUrl, value); }
}
public CarDetailsPageViewModel() { }
public void OnNavigatedTo(INavigationParameters parameters)
{
if (parameters.ContainsKey("selectedCar"))
{
Car car = parameters.GetValue<Car>("selectedCar");
if (car != null)
{
CarTitle = $"{car.Make} {car.Model}";
PhotoUrl = car.PhotoUrl;
}
}
}
public void OnNavigatedFrom(INavigationParameters parameters) { }
}
From this answer and example, you can see:
How to, use EventToCommand behaviour with ListView
Define and use DelegateCommand with passing parameter
How to navigate to another view and pass navigation parameter and
... finally how to access the passed data.
Code and this sample you can find on my GitHub profile here.
Hope this answer was helpful for you!
Wishing you lots of luck with coding! 👋
Here is the class of ChildViewModel:
public class ChildViewModel : Screen
{
private string imie = string.Empty;
private string nazwisko = string.Empty;
private string wiek = string.Empty;
private Person person;
private ObservableCollection<Person> personColl;
private MainViewModel mainView = new MainViewModel();
public ChildViewModel(Person person, ObservableCollection<Person> personColl)
{
this.person = person;
this.personColl = personColl;
this.Wyswietl();
}
public string ImieTxt
{
get => this.imie;
set
{
this.imie = value;
this.NotifyOfPropertyChange(() => this.ImieTxt);
}
}
public string NazwiskoTxt
{
get => this.nazwisko;
set
{
this.nazwisko = value;
this.NotifyOfPropertyChange(() => this.NazwiskoTxt);
}
}
public string WiekTxt
{
get => this.wiek;
set
{
this.wiek = value;
this.NotifyOfPropertyChange(() => this.WiekTxt);
}
}
public void Zmien()
{
this.personColl[mainView.DataGridIndex].Imie = this.ImieTxt;
this.personColl[mainView.DataGridIndex].Nazwisko = this.NazwiskoTxt;
this.personColl[mainView.DataGridIndex].Wiek = this.WiekTxt;
this.TryClose();
}
private void Wyswietl()
{
this.ImieTxt = this.person.Imie;
this.NazwiskoTxt = this.person.Nazwisko;
this.WiekTxt = this.person.Wiek;
}
}
I have no idea how to upload new data from ChildView to dataGrid in MainView, after clicking button "Zmien". In MainView I have dataGrid, where from MainViewModel I'm loading data from the list. After clicking button "Zmien", new data doesn't load in dataGrid.
Maybe you have any idea how to do it?
From my article on Codeproject Guide to WPF DataGrid Formatting Using Bindings:
Connecting a DataGrid with Business Data
Even connecting a DataGrid with the business data is not trivial. Basically, a CollectionViewSource is used to connect the DataGrid with the business data:
The CollectionViewSource does the actual data navigation, sorting, filtering, etc.
<Window.Resources>
<CollectionViewSource x:Key="ItemCollectionViewSource" CollectionViewType="ListCollectionView"/>
</Window.Resources>
<DataGrid
DataContext="{StaticResource ItemCollectionViewSource}"
ItemsSource="{Binding}"
AutoGenerateColumns="False"
CanUserAddRows="False">
//create business data
var itemList = new List<stockitem>();
itemList.Add(new StockItem {Name= "Many items", Quantity=100, IsObsolete=false});
itemList.Add(new StockItem {Name= "Enough items", Quantity=10, IsObsolete=false});
...
//link business data to CollectionViewSource
CollectionViewSource itemCollectionViewSource;
itemCollectionViewSource = (CollectionViewSource)(FindResource("ItemCollectionViewSource"));
itemCollectionViewSource.Source = itemList;
Define a CollectionViewSource in Windows.Resource
The gotcha here is that you must set the CollectionViewType. If you
don't, the GridView will use BindingListCollectionView, which does
not support sorting. Of course, MSDN does not explain this anywhere.
Set the DataContext of the DataGrid to the CollectionViewSource.
In the code behind, find the CollectionViewSource and assign your business data to the Source property
In this article, data gets only read. If the user should be able to edit the data, use an ObservableCollection. However, it is often better to leave the DataGrid readonly, because editing in the DataGrid behaves differently from what one is used from spreadsheet programs. It might be better if the user has to doubleclick on the row he wants to change and open another window just for editing that entity or adding a new one.
I have read loads of posts about this topic, but I cannot for the life of me figure this out, so your help is appreciated as I am losing the will to live!
I am trying to bind a list to a combobox in WPF, here is my code:
ViewModel:
public class ViewModelAddRegion
{
public List<DataAccessLayer.Model.CountryList> CountryList { get; set; }
public object GetCountryList()
{
List<DataAccessLayer.Model.CountryList> CountryList = new List<DataAccessLayer.Model.CountryList>();
CountryList = Data.DatabaseGets.GetAllCountries();
return CountryList;
}
}
So that gets my list. In the backing to my window, the code is:
public AddRegion()
{
var vm = new WineDatabase.ViewModel.ViewModelAddRegion();
var CountryAllList = vm.GetCountryList();
DataContext = CountryAllList;
InitializeComponent();
}
And finally, in my window:
<ComboBox Name="CountryList"
Margin="159,0,-160,0"
Grid.Row="1"
Grid.RowSpan="2"
ItemsSource="{Binding CountryAllList}"
DisplayMemberPath="CountryName"/>
Debugging, my list is populated as expected, but the combobox is forever empty.
Thanks for any assistance at all!
CountryAllList is just a local variable that you can't bind to. See the Data Binding Overview article on MSDN for details.
You should assign the ViewModel instance to the DataContext
var vm = new WineDatabase.ViewModel.ViewModelAddRegion();
vm.CountryList = vm.GetCountryList();
DataContext = vm;
and bind to its CountryList property
<ComboBox ItemsSource="{Binding CountryList}" ... />
Finally, in your GetCountryList method, it doesn't make much sense to assign the return value of Data.DatabaseGets.GetAllCountries() to a local variable. You could instead directly return it from the method.
public List<DataAccessLayer.Model.CountryList> GetCountryList()
{
return Data.DatabaseGets.GetAllCountries();
}
The GetCountryList() method may as well directly assign to the CountryList property
public void GetCountryList()
{
CountryList = Data.DatabaseGets.GetAllCountries();
}
and you could write the initialization code like this:
var vm = new WineDatabase.ViewModel.ViewModelAddRegion();
vm.GetCountryList();
DataContext = vm;
Change AddRegion method to:
public AddRegion()
{
var vm = new WineDatabase.ViewModel.ViewModelAddRegion();
vm.CountryList = vm.GetCountryList();
DataContext = vm;
InitializeComponent();
}
And in ComboBox set ItemsSource="{Binding CountryList}"
In my WPF C# project I have two frames in the MainWindow. The first frame has a page with a DataGrid (bound to an XML file) in which I select an object of interest.
<Grid.Resources>
<XmlDataProvider x:Key="XmlData" Source="/DB.xml"/>
</Grid.Resources>
<DataGrid Name="dg"
SelectionChanged="dg_SelectionChanged"
AutoGenerateColumns="False"
ItemsSource="{Binding Source={StaticResource XmlData}, XPath=Data/Object}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding XPath=Type}"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding XPath=Number}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
In the second frame I open different pages (one at a time) according to the calculations I am going to perform with the selected object. At every SelectionChanged event a custom method MySub() is called, that initiates all the necessary calculations on the loaded page.
public partial class pg_DB : Page
{
public pg_DB()
{
InitializeComponent();
}
public void dg_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
switch (Var._loadedPage) // This variable holds the name of the loaded page.
{
case "pg_SCT":
pg_SCT c1 = new pg_SCT();
c1.MySub(); // Initiates the calculation process on pg_SCT page.
break;
case "pg_OCT":
pg_OCT c2 = new pg_OCT();
c2.MySub(); // Initiates the calculation process on pg_OCT page.
break;
}
}
}
The problem is that everything works well except the data visualization. Thus, for instance, every time the MySub() is called the List<> is being updated and the ItemsSource has the necessary items, yet they are not displayed in the DataGrid. Moreover, even simple TextBox1.Text = "Test" is not working. At the same time the same code works perfectly from the Button_Click method.
public partial class pg_SCT : Page
{
public pg_SCT()
{
InitializeComponent();
//grid.ItemsSource = myList (); (This works).
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//grid.ItemsSource = myList (); (This works).
//TextBox1.Text = "Test"; (This works).
}
public void MySub()
{
grid.ItemsSource = myList(); // Nothing happens (although debugging shows that List is updated and ItemsSource has necessary items).
TextBox1.Text = "Test"; // Textbox remains empty.
}
public class Author
{
public int ID { get; set; }
public string Name { get; set; }
}
private List<Author> myList()
{
List<Author> authors = new List<Author>();
authors.Add(new Author()
{
ID = Var._ID,
Name = Var._Name,
});
return authors;
}
}
I can’t find what is missing to populate DataGrid and TextBox from my custom method MySub().
Thank you for your time and consideration.
In your dg_SelectionChanged method, you're creating instances of your Page controls as local variables but not using them anywhere so they are just going out of scope. I'm guessing you probably have other instances you've created elsewhere that are the ones being displayed but based on the code here the ones calling MySub are never going to show up.
I'm trying to create DataGrid in a separate UserControl whose DataContext is a List of T.
In the code behind, I create a List, populate the list, then send it to the constructor for the UserControl on which I have the DataGrid I am trying to populate.
The UserControl class is as follows.
public partial class QuotePreview : UserControl
{
private static SelectionList previewList = new SelectionList();
public SelectionList PreviewList
{
get { return previewList; }
}
public QuotePreview()
{
InitializeComponent();
}
public QuotePreview(SelectionList selectedOptions)
{
InitializeComponent();
previewList = selectedOptions;
QuotePreviewDataGrid.DataContext = previewList;
}
}
And the Xaml looks like:
<DataGrid Name="QuotePreviewDataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Header="Model Number" Binding="{Binding ModelNumber}"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}"/>
<DataGridTextColumn Header="List Price per Unit" Binding="{Binding Price}"/>
</DataGrid.Columns>
</DataGrid>
I've tried setting the ItemSource as well using
QuotePreviewDataGrid.ItemsSource = PreviewList;
I've also tried setting both the data context and the itemsource as well as refreshing:
QuotePreviewDataGrid.Items.Refresh();
The databinding I have set to listboxes in the rest of my application works perfectly. In the list boxes I have the itemsource set to {Binding} and the ListItems binding set to {Binding Property}. The datacontext for the listboxes set in the code behind.
My datagrid here is setup in the same manner, yet for some reason nothing is being displayed inside the grid.
When I go through the debugger and watch the flow of information, I can see the List of T, SelectionsList being created and passed to the constructor for the user control where the data grid lies. I can see that the DataContext is indeed being set and shows the items in the list, but when I go back to my appication and try to view the data grid, it's blank.
Any help would be greatly appreciated. I've been trying to wrap my mind around this problem for the last day and a half. Thanks!
UPDATE
The SelectionList is setup like:
public class SelectionList : List<Selection>
{
public List<Selection> availableSelections = new List<Selection>();
public List<Selection> AvailableSelections
{
get { return availableSelections; }
}
}
and a Selection is then defined by:
public class Selection : DependencyObject
{
public bool IsChecked { get; set; }
public string ModelNumber { get; set; }
public string Description { get; set; }
public string Price { get; set; }
}
When the application starts, I build a catalog of existing products (Selections). On different tabs, one for each product family, the datacontext for the products list box is initialized with with available products that it grabs from the catalog. Then pending which product a user selects, the available options or child selections associated with that product are populated into the appropriate list boxes, accessories and warranties.
Once a user selects the options they want, a button is clicked to preview the selected items which is supposed to populate the data grid explained above.
I can build the list of selected options, however when I try to set the data context of the data grid, nothing appears. The Lists for available selections are built and set to the appropriate data context the same way I am trying to do it for the data grid, however the data grid doesn't want to display my information.
UPDATE
So after some more debugging, I've narrowed the problem down a bit. The data binding works as it should. I have no real problems there, I don't think. However, the issue I'm running into now is what I believe to be 2 different instances of my User Control, but only the original is being displayed, not the updated copy.
Here's a copy of the class from about with a couple lines I added to help debug the problem.
public partial class QuotePreview : UserControl
{
private SelectionList _selectionList;
private SelectionList temp;
public QuotePreview()
{
InitializeComponent();
_selectionList = (SelectionList)this.DataContext;
}
private void QuotePreview_Loaded(object sender, RoutedEventArgs e)
{
_selectionList.SelectedOptions.Add(
new Selection
{
ModelNumber = "this",
Description = "really",
Price = "sucks"
});
}
public QuotePreview(SelectionList selectedOptions)
{
InitializeComponent();
_selectionList = (SelectionList)this.DataContext;
temp = selectedOptions;
_selectionList.AddRange(selectedOptions);
QuotePreview_Loaded();
}
private void QuotePreview_Loaded()
{
foreach (var options in temp.SelectedOptions)
{
_selectionList.SelectedOptions.Add(options);
}
QuotePreviewDataGrid.ItemsSource = _selectionList.SelectedOptions;
}
}
The implementation of the default constructor, is called every time the user control / tab, is clicked on. When that happens, _selectionList is set to the data context of the user control, followed by the Loaded Event which adds a line to my data grid.
In another user control where I select the options I want to add to my data grid user control, I click a button that creates a list of the options I want to be added and calls the custom constructor I wrote. Once the constructor finishes, it calls a custom Loaded Event method that I created for shits and giggles, that adds the selected options to my _selectionList.
Now once I click on the data grid user control again, it goes through the whole default process, and adds another default line.
If I go back a tab and say I want these options again and go back to the data grid, it again goes through the default process and adds another default line.
Whats most intriguing though is that I can see both of the selectionLists build since I dont clear the in between processes. I see a list build of the options i want to display and a list build of the default options build...
Oh, also, SelectionList does implement ObservableCollection.
I finally came up with a solution to the problem.
public static class QuotePreview
{
public static ObservableCollection<PurchasableItem> LineItems { get; private set; }
static QuotePreview()
{
LineItems = new ObservableCollection<PurchasableItem>();
}
public static void Add(List<PurchasableItems> selections)
{
foreach (var selection in selections)
{
LineItems.Add(selection);
}
}
public static void Clear()
{
LineItems.Clear();
}
}
public class QuoteTab : TabItem
{
public ObservableCollection<PurchasableItem> PreviewItems { get; private set; }
public QuoteTab()
{
Initialize()
PreviewItems = QuotePreview.LineItems;
DataGrid.ItemSource = PreviewItems
}
}
Try changing:
QuotePreviewDataGrid.DataContext = previewList;
to
this.DataContext = previewList;
My suspicion is that the ItemsSource="{Binding}" in your xaml is overriding the DataContext code in your constructor.
By changing the previewList to be DataContext of the entire UserControl, then the binding of the DataGrid's ItemsSource can correctly evaluate.
On a side note, I would start looking into the use of ObservableCollection<T> and the MVVM design pattern. An issue you might end up with is that your DataGrid doesn't update when the underlying list changes, using the ObservableCollection<T> will fix this.
Using the MVVM design pattern will give you a good separation of your logic and data (in this case your list and how it's loaded) from the physical display (the DataGrid)