I would like to print the movie information from the tmdb api and print the movie information into the usercontrols. I want the program to show a poster, its name, its release date, how many votes and so on. My problem starts right here, I want to load usercontrolls as async, so I want all of them loaded in my main window at the same time. But I can't do it, they're loading one by one. Is there a way to use Async in the UI update? Or is there another way to achieve this? I want to get the names of all 20 movie posters at the same time as the webpages and get these 20 usercontrol added to my main window at the same time. I am using this code right now:
private async void Page_Loaded(object sender, RoutedEventArgs e)
{
await GetPopularMoviesAsync();
}
public async Task GetPopularMoviesAsync()
{
SearchContainer<SearchMovie> popularMovies = await client.GetMoviePopularListAsync("en", 1);
List<SearchMovie> popularMovieList = popularMovies.Results;
foreach (var searchMovie in popularMovieList)
{
MovieUSC mov = new MovieUSC();
var image = await GetMovieImage(searchMovie);
GetPosterFromFile(image, mov.MoviePoster);
mov.Name = "PopularMovies" + searchMovie.Id;
mov.MovieName.Text = searchMovie.OriginalTitle;
mov.MovieReleaseDate.Text = "(" + searchMovie.ReleaseDate.Value.Year + ")";
mov.MovieRatingBar.Value = Convert.ToInt32(searchMovie.VoteAverage) / 2;
mov.ClickedMovie += ClickedMovie;
MoviePanel.Items.Add(mov);
}
}
Even if you don't want to go the whole MVVM route, it's still good practice to separate data access from the UI.
Use an ObservableCollection<> to hold the search results, which will be the driver of the display - this will automatically update any bound controls each time it is refreshed.
public ObservableCollection<SearchMovie> PopularMovies { get; }
= new public ObservableCollection<SearchMovie>();
private async void Page_Loaded(object sender, RoutedEventArgs e)
{
var popularMovies = await client.GetMoviePopularListAsync("en", 1);
PopularMovies.Clear();
foreach(var movie in popularMovies)
PopularMovies.Add(movie);
}
Your window should use either a ListBox if you want the user to be able to select a specific movie item, otherwise an ItemsControl. In either case, set ItemsSource to the PopularMovies collection. Use a DataTemplate to define the layout for each movie item - each control is bound to the appropriate property of the SearchMovie object.
<ItemsContol x:Name="MoviesDisplay">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding OriginalTitle}" />
<TextBlock Text="{Binding ReleaseDate.Value.Year}" />
// Add more controls here as required
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Related
Pulling my hair out at the point. My Picker is showing an annoying line on the UI and/or the Picker's Title property if that's enabled. I simply want the Picker, not the stuff showing on the UI beneath it. Any idea on how to achieve this? Do I have to use a custom renderer or is there something simple I'm missing?
Note: The list is intentionally empty in the below examples.
Without the title, I click the Existing button, the line shows, click it again and the modal appears:
With the title, I click the Existing button, the line and title show, click it again and the modal appears:
Don't know why I have to click the button twice. But it's only on the initial page load. If I exit the modal and click the button again, it immediately appears, no double-click. Not sure if that's related to my original question, but thought I'd include it for additional information.
NewSubjectPage.xaml (chopped for brevity)
<ContentPage.Content>
<StackLayout x:Name="NewSubjectMainLay">
<ScrollView>
<StackLayout x:Name="NewSubjectChildLay">
<Grid>
<Button
x:Name="NewSubjectExisChrtBtn"
Clicked="NewSubjectExisChrtBtn_Clicked"
Grid.Column="2"
Text="Existing" />
</Grid>
</StackLayout>
</ScrollView>
<Picker
x:Name="NewSubjectExisChrtPck"
IsVisible="False"
ItemsSource="{Binding Charts}"
ItemDisplayBinding="{Binding Name}"
SelectedIndexChanged="NewSubjectExisChrtPck_SelectedIndexChanged"
Title="Select chart"
Unfocused="NewSubjectExisChrtPck_Unfocused"/>
</StackLayout>
</ContentPage.Content>
NewSubjectPage.xaml.cs (chopped for brevity)
public partial class NewSubjectPage : ContentPage
{
private string chartName;
private readonly NewSubjectViewModel _viewModel;
public string ChartName
{
get => chartName;
private set
{
chartName = value;
OnPropertyChanged();
}
}
public NewSubjectPage()
{
InitializeComponent();
BindingContext = _viewModel = new NewSubjectViewModel();
chartName = "";
}
private void NewSubjectExisChrtBtn_Clicked(object sender, EventArgs e)
{
_viewModel.LoadChartsCommand.Execute(null);
NewSubjectExisChrtPck.IsVisible = true;
NewSubjectExisChrtPck.Focus();
}
private void NewSubjectExisChrtPck_SelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;
if (selectedIndex != -1)
{
ChartName = picker.Items[picker.SelectedIndex];
}
}
private void NewSubjectExisChrtPck_Unfocused(object sender, FocusEventArgs e)
{
NewSubjectExisChrtPck.IsVisible = false;
NewSubjectExisChrtPck.Unfocus();
}
}
NewSubjectViewModel.cs (chopped for brevity)
class NewSubjectViewModel : BaseViewModel
{
private ObservableCollection<Chart> charts;
public ObservableCollection<Chart> Charts
{
get { return charts; }
private set
{
charts = value;
OnPropertyChanged();
}
}
public Command LoadChartsCommand { get; set; }
public NewSubjectViewModel()
{
LoadChartsCommand =
new Command(
async () => await ExecuteLoadChartsCommand()
);
}
private async Task ExecuteLoadChartsCommand()
{
try
{
IndicatorRunning = true;
var list = await App.Database.GetChartsAsync();
Charts = new ObservableCollection<Chart>(list);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
}
Thanks for your help! Let me know if you need to see anything else.
First, I was not able to reproduce the issue of the modal not showing until a second click of the button. You might need to provide more code for that to happen. To even use your code sample I had to replace var list = await App.Database.GetChartsAsync(); with something else to simulate a long running task that returns an empty list. Also had to create a Chart type with a Name property. Not to mention BaseViewModel. In the future, please provide all code to reproduce the issue so there is minimal work required of the person who is trying to help you. There is concept on Stack Overflow called the MCVE (minimal, complete, verifiable example): http://stackoverflow.com/help/mcve
That said, perhaps the first click is actually focusing the emulator and making it the active app, and then the second is the first actual click on the button? This I can reproduce. IOW, if the emulator is not the foreground app, you have to click it once to make it active and then your app will handle clicks.
As for the undesirable UI, you do realize that the Picker UI is basically a clickable label that when clicked displays the actual picker modal? So when you make it visible, what you are making visible is the label UI, which has the line and the Title (if set), and when you focus that label, then the actual picker dialog is displayed. If you don't want to see the UI Label at all, then why make it visible? You can focus it without making it visible, so just remove the line NewSubjectExisChrtPck.IsVisible = true;
As a side note, when you call _viewModel.LoadChartsCommand.Execute(null); that calls an async method, var list = await App.Database.GetChartsAsync(); , so the LoadChartsCommand returns before you set the Charts property, and also then the code following the call to _viewModel.LoadChartsCommand.Execute(null); also executes before LoadChartsCommand really finishes, so you are making the picker visible and focusing it before the LoadChartsCommand finishes as well, so if you were loading actual items for the picker to display, they may not be there the first time. Maybe it's just the sample code, but I see no reason to use a command here, but rather you should just call an awaitable task. You are not binding to the LoadChartsCommand, so I see no reason for you to even use a Command in this scenario. Instead I suggest making ExecuteLoadChartsCommand public and calling it directly, e.g.:
private async void NewSubjectExisChrtBtn_Clicked(object sender, EventArgs e)
{
//_viewModel.LoadChartsCommand.Execute(null); // Returns immediately, so picker not loaded with items yet.
await _viewModel.ExecuteLoadChartsCommand(); // Waits for method to finish before before presenting the picker.
//NewSubjectExisChrtPck.IsVisible = true;
NewSubjectExisChrtPck.Focus();
}
I have an ASP.NET Web API which works all fine according to my tests. Now, I want to consume it in a simple WPF application. I just want to get the data from API, display it in the client app, and then to add or delete the data from the database just by using the client app.
My problem is that when I pass the data to a ListBox, I kind of lose information, specifically I lose the Id of the object, which I need later in order to delete it from the database. In my scenario, I have something like this in one of my pages:
<ListBox
Name="carCategoryListBox"
Grid.Row="2"
Grid.Column="1" />
And then, in the .cs file of the page, I do something like this:
private void Page_Loaded(object sender, RoutedEventArgs e)
{
BindCarCategoryList();
}
private async void BindCarCategoryList()
{
var client = CarHttpClient.GetClient();
HttpResponseMessage response = await client.GetAsync("api/carcategories");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var carCategories = JsonConvert.DeserializeObject<List<DTO.CarCategory>>(content);
carCategoryListBox.ItemsSource = carCategories.Select(cc => cc.Category).ToList();
}
else
{
MessageBox.Show("Error code " + response.StatusCode + "\nMessage - " + response.ReasonPhrase);
return;
}
}
As you can see I just get the Category fields of my objects, and pass them to the ListBox to be displayed. The CarCategory class is pretty simple and can be seen below:
public class CarCategory
{
public int Id { get; set; }
public string Category { get; set; }
}
The problem is that at a later stage I need to get the actual Id of the object, and use it. But, since I pass only the Category field to the ListBox, this Id information is somehow lost in my case. If I do something like this:
int id = carCategoryListBox.SelectedIndex;
Then, I just get the Id of the item in the ListBox (which item it is in the list), I don't actually get the original Id of the object that is stored in the database. Any ideas how to pass the data to a ListBox in such a way that I still somehow preserve the original Id information?
ListBox.SelectedIndex is a position of selected item in ItemsSource. It can even be -1 if there is no selected item. And it is not the same value as Id
to get databound object make some changes:
1) use List<CarCategory> as ItemsSource:
carCategoryListBox.ItemsSource = carCategories;
2) set ListBox.DisplayMemberPath for it to display category name:
<ListBox DisplayMemberPath="Category"
Name="carCategoryListBox"
Grid.Row="2"
Grid.Column="1" />
3) use SelectedItem property to get selected item (can be null):
var category = (CarCategory)carCategoryListBox.SelectedItem;
You should bind the entire object not select just a field from it, and set the list box's DisplayMemberPath to 'Category'. Then your selected item will be the entire CarCategory, and you can get its ID.
You should also look at MVVM and try to get as close as possible to the concepts, they really help.
You have to change your BindCarCategoryList
carCategoryListBox.ItemsSource = carCategories.Select(cc => cc.Category).ToList();
to
carCategoryListBox.ItemsSource = carCategories;
and modify your xaml like this
<ComboBox Grid.Row="2" Grid.Column="1" Margin="4" Width="250" DisplayMemberPath="Category" DisplayMemberPath="Id" />
or if you dont use MVVM assign DisplayMemberPath and DisplayMemberPath directly in your .cs file
I have an issue with a GridView in a UWP application that I'm working on...
Items in the GridView load correctly, however items that are out of view (off the page and not visible) do not have a DataContext assigned, and no event ever fires when the DataContext is assigned. Various bindings do work as TextBlocks that are bound get updated, but the the normal event workflow and Loaded events get all strange.
<GridView Grid.Row="1" Name="SearchGrid" ItemsSource="{Binding SearchItems}" ItemClick="SearchGrid_ItemClick">
<GridView.ItemTemplate>
<DataTemplate>
<local:RsrItemGridViewItem />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
The grids all show correctly, except, for being able to properly delay load some items because the DataContext isn't set at time of load (and a DataContextChanged event isn't fired when the context is updated).
Does anyone have any ideas how to get notified when the control becomes visible? This seems like a notification bug, or there is some binding thing that I'm missing.
Thank you!
Does anyone have any ideas how to get notified when the control becomes visible?
You can't use FrameworkElement.Loaded event here to get notify when your RsrItemGridViewItem becomes visible, this event occurs when a FrameworkElement has been constructed and added to the object tree, and is ready for interaction.
GirdView control implements UI virtualization for better UI performance, if your GridView is bound to a collection of many items, it might download only items 1-50, When the user scrolls near the end of the list, then items 51 – 100 are downloaded and so on. But for example, there are only 20 items now be shown, but it might have loaded 45 items, 25 items could not be seen in this moment.
If you change the default ItemsPanel of GridView which is ItemsWrapGrid to for example VariableSizedWrapGrid, GridView will lose virtualization, and all items will be loaded at the same time even most of them can not be seen at one moment.
For you problem, I think what you can give a try is calculating the ScrollViewer's VerticalOffset with your GridView's height and the items's count be shown, and then you can know which items are been shown at this moment.
For example here:
private ObservableCollection<MyList> list = new ObservableCollection<MyList>();
public MainPage()
{
this.InitializeComponent();
this.Loaded += MainPage_Loaded;
}
private double viewheight;
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var scrollViewer = FindChildOfType<ScrollViewer>(gridView);
scrollViewer.ViewChanged += ScrollViewer_ViewChanged;
viewheight = gridView.ActualHeight;
}
private void ScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
var scrollViewer = sender as ScrollViewer;
var Y = scrollViewer.VerticalOffset;
//calculate here to get the displayed items.
}
public static T FindChildOfType<T>(DependencyObject root) where T : class
{
var queue = new Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
DependencyObject current = queue.Dequeue();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(current); i++)
{
var child = VisualTreeHelper.GetChild(current, i);
var typedChild = child as T;
if (typedChild != null)
{
return typedChild;
}
queue.Enqueue(child);
}
}
return null;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
list.Clear();
for (int i = 0; i < 200; i++)
{
list.Add(new MyList { text = "Item " + i });
}
}
Since GridView control's layout is adaptive to the app's size, the current displayed count is dynamic, you can try other height based properties (for example each item's height) and the ScrollViewer's VerticalOffset to calculate, there is no ready-made method to get your work done, it's a little complex to calculate, but I think there is no better solution for now.
After doing some testing with this, what I found out worked (though it's not very clean, and I believe there is a bug with bindings) was to add the custom control to the GridView, then in the grid view adding a DataContext={Binding} to the Image I wanted to get notified of an update on.
<UserControl ...><Image DataContext="{Binding}" DataContextChanged="ItemImage_DataContextChanged" /></UserControl>
The main control doesn't get notified of a DataContext change, but the child elements are notified.
I'm a bit new to WPF/XAML (though I've learnt C#) and would really appreciate any help for my question. I did look around other posts and google for a while but I can't seem to find a satisfactory or detailed answer to get me going on with my project. Please look below for details. Thanks you in advance!
Objective
I have a class called Tile that consists of a few properties and an event handler.
I also have an ItemControl that has a button (as by the DataTemplate), and whose ItemSource is a collection of Tiles.
Now, I want to bind the "Click" event of the Button so as to invoke the Event Handler method defined in the class Tile.
In other words when I click the button of any item in the ItemControl, the method handler of the corresponding Tile instance (from the collection) must be invoked. How would I tackle this problem?
Below is the entire code, simplified to avoid distraction:
XAML
<Window x:Class="SampleWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<!-- Make a ItemControl for "Tile"s. -->
<ItemsControl x:Name="TileList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- Wire the click event of this Button
to event handler in the Tile class. -->
<Button Content="Show"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
CODE-BEHIND
namespace SampleWPF
{
public partial class MainWindow : Window
{
ObservableCollection<Tile> tiles;
public MainWindow()
{
InitializeComponent();
// Adding some sample data for testing.
tiles = new ObservableCollection<Tile>();
tiles.Add(new Tile("Item 1"));
tiles.Add(new Tile("Item 2"));
TileList.ItemsSource = tiles;
}
}
public class Tile : INotifyPropertyChanged
{
public string Data
{ /* Accessors and PropertyNotifiers */ }
public Tile(string data)
{ /* Initializing and assigning "Data" */ }
// INotifyPropertyChanged implementation...
// { ... }
// This event handler should be bound to the Button's "Click" event
// in the DataTemplate of the Item.
public void ShowButton_Click(object sender, EventArgs e)
{
MessageBox.Show("Viewing item from: " + this.Data);
}
}
}
Hence, if I click the first "Show" button, the output should be "Viewing item from: Item 1" and if I click the second "Show" Button, the output should be "Viewing item from: Item 2".
So what is the recommended/efficient way to do this? Is my code inappropriate for this requirement?
Event handlers are the wrong approach - use Commands and more importantly MVVM.
As I can see that you are new (and probably from a WinForms or ASP.NET background) you should read this blog to understand how your thinking needs to change - this is the most important part to understand before tackling WPF: http://rachel53461.wordpress.com/2012/10/12/switching-from-winforms-to-wpfmvvm/
You should also read Kent Boogart's blog on how MVVM works from base principles: http://kentb.blogspot.co.uk/2009/03/view-models-pocos-versus.html
Let me start with some basics:
Don't assign itemsource in codeBehind - use Binding like this:
<Controll ItemSource="{Binding MyObservableCollection}"/>
There are many ways You can achieve this. I think that using this.Data is not the best solution for this.
For example if Your tail have ID or something You can assign this id to button CommandParameter like below
<Button CommanParameter="{Binding Path=ID}" Click="ShowButton_Click"/>
And then in Your button_click event u can 'catch' this like this:
public void ShowButton_Click(object sender, EventArgs e)
{
int ID = int.Parse(((Button)sender).CommandParameter.ToString());
}
EDIT
To use this binding You need to set DataContext. You can do this in ctor like this:
public MainWindow()
{
InitializeComponent();
// Adding some sample data for testing.
tiles = new ObservableCollection<Tile>();
tiles.Add(new Tile("Item 1"));
tiles.Add(new Tile("Item 2"));
// below You are setting a datacontext of a MainWindow to itself
this.DataContext = this;
}
ANOTHER EDIT
Let's assume Your tail class have property called ID. If You bound this ID to Button.CommandParameter You can later retrieve the tile with linq like this:
public void ShowButton_click(object sender, EventArgs e)
{
int MyId = int.Parse(((Button)sender).CommandParameter.ToString());
Tile TileIWasSearchingFor = (from t in tiles where t.ID == MyId select t).First();
// do something with tile You found
}
Well since my requirement was rather "simple", I've managed a work around, avoiding commands. Thanks to the answer here by MajkeloDev: https://stackoverflow.com/a/27419974/3998255 for guidance.
This is the final event handler:
public void ShowButton_Click(object sender, EventArgs e)
{
Tile requestingTile = (sender as Button).DataContext as Tile;
if(requestingTile != null)
MessageBox.Show("Viewing item from: " + this.Data);
// Or whatever else you want to do with the object...
}
Also, adding the ItemSource as a XAML attribute:
<ItemsControl x:Name="TileList" ItemsSource="{Binding tiles}">
And setting DataContext in constructor of MainWindow:
public MainWindow()
{
this.DataContext = this;
// Whatever else you want to do...
}
Well it works as required.
I have a ListBox and a class with strings. Each time that a user clicks add button in the application, I create a new instance of the class and add it to the list which is binded to the ListBox. The first time I click the add button, the list box shows the first item, but the next time it doesn't show two items.
XAML - this is the ListBox:
<ListBox Name="ListBox_BinsRegion" Height="181" Margin="233,16,6,94" Width="253" Background="Transparent" BorderThickness="1" BorderBrush="Black" ScrollViewer.VerticalScrollBarVisibility="Auto" ItemsSource="{Binding}"/>
The code behind:
List<Class_ListViewItem> List_ListBoxItems = new List<Class_ListViewItem>();
private void Button_Add_Click(object sender, RoutedEventArgs e)
{
Class_ListViewItem item = new Class_ListViewItem();
item.WH = this.comboBox_WareHouseBinsRegionDefinition.SelectedItem.ToString();
item.XXFrom = textBox_XXFrom.Text;
item.XXTo = textBox_XXTo.Text;
item.YYFrom = textBox_YYFrom.Text;
item.YYTo = textBox_YYTO.Text;
item.Z = textBox_ZFrom.Text;
List_ListBoxItems.Add(item);
ListBox_BinsRegion.DataContext = List_ListBoxItems;
}
Where is my mistake?
WPF does not know when your collection is changing. The problem is here:
List<Class_ListViewItem> List_ListBoxItems = new List<Class_ListViewItem>();
you need to change the list to
ObservableCollection<Class_ListViewItem> List_ListBoxItems = new ObservableCollection<Class_ListViewItem>();
ObservableCollection (System.Collections.ObjectModel) throws an event when the collection is changed, so that WPF can update the listbox.
Also, you can remove the following line, or move it to the constructor of your control.
ListBox_BinsRegion.DataContext = List_ListBoxItems;
You should not change the DataContext of the control, instead set the binding to theList_ListBoxItems and make it a public property, and use an ObservableCollection or BindableCollection instead of list
When you assign the DataContext the second time, it doesn't technically change. This is because you are assigning it to the same collection. You should do something like this instead:
ObservableCollection<Class_ListViewItem> List_ListBoxItems = new ObservableCollection<Class_ListViewItem>();
public YourControl() {
InitializeComponent();
ListBox_BinsRegion.DataContext = List_ListBoxItems;
}
private void Button_Add_Click(object sender, RoutedEventArgs e)
{
Class_ListViewItem item = new Class_ListViewItem();
item.WH = this.comboBox_WareHouseBinsRegionDefinition.SelectedItem.ToString();
item.XXFrom = textBox_XXFrom.Text;
item.XXTo = textBox_XXTo.Text;
item.YYFrom = textBox_YYFrom.Text;
item.YYTo = textBox_YYTO.Text;
item.Z = textBox_ZFrom.Text;
List_ListBoxItems.Add(item);
}
Use an ObservableCollection<> rather than a List<>. This will update the binding automatically, with no need for the following line (which can be kind of slow)
ListBox_BinsRegion.DataContext = List_ListBoxItems;
You could either do what everyone else already suggested (using an ObservableCollection instead of the List) - or you could query the dependency property which is bound and find the corresponding Binding and refresh it manually.
I'd go for the ObservableCollection :)