Partial update of Label text in XamarinForms - c#

I am having a rotator having label in each slide indicating the count to be displayed there. I have bounded it property which performs an API operation for fetching the count. I getting the getting the value fetched but the problem is that when displayed on the label it visible only partially. I am familiar with the rules about updating UI elements on the UI thread using the Device.BeginInvokeOnMainThread. I have called with Device.BeginInvokeOnMainThread. For eg if the count is 25. but the label shows only 5. but when the page is refreshed the the same command gets executed and displays the correct value.I have followed viewmodel approach i.e i have command for fetching the count.
I have called that command during the page on appearing event also To refresh the count when I am back to home page even after initial load.
Xaml
<Label FontFamily="{StaticResource Montserrat-Regular}"
HorizontalOptions="StartAndExpand"
Text="{Binding Count}"
FontSize="60"
Style="{StaticResource RotorLabelStyle}" />
viewmodel
await Task.Run(async () =>
{
var result = await _matchingTrialsService.GetMatchingTrialsCount();
Device.BeginInvokeOnMainThread(() =>
{
item.Count = $"{result:0}";
});
});
xaml.cs
protected override void OnAppearing()
{
base.OnAppearing();
Device.BeginInvokeOnMainThread(() => {
((PatientHomeViewModel)this.BindingContext).CountDetilsCommand.Execute(null);
});
}
Does anybody have a workaround for this issue
left is the result is truncated result
right is the correct result

Related

Async Loading UserControls in ItemControl WPF C#

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>

Scrolldown in ListView using ItemAppearing event skips records in Xamarin.Forms

I have ListView in Xamarin Page. I use ItemAppearing event to scroll down. ListViewCell height is big so 1 screen cover first and 80% part of second viewcell.
Steps to load more data for scroll down:
Initially when page load, it call API and get 10 records in
EmployerResult List. That will be added in ListView using
databinding.
Having ItemAppearing event. There is a condition in this event.
When last cell start to appear, it will call API and again append 10
record in List object of ViewModel.
So, this way it will call to API and append 10 record everytime when
last cell start to appear.
Now the point is on every load, it skips last record and show first record of next 10 record. However sometimes it skips 2-3 records if user scrolldown fastly.
i.e if I have 10 records first time. Now I am on 9th record and I am scrolling down to 10. 10th record is starting to appear and API call fires. After this call completed, screen will show 11th record at top of the screen. Here, 10th record is skipped. This way user will see 11th record rather 10th. Here user need to scrollup again to see 10th record.
Sometimes, it skips 2-3 records if user scrolldown fastly.
Can anybody please suggest me?
Code
XAML
<ListView Grid.Row="0" x:Name="EmployerResultsListView"
ItemsSource="{Binding EmployerResults}"
HasUnevenRows = "true"
SeparatorVisibility="None"
IsPullToRefreshEnabled="true"
RefreshCommand="{Binding RefreshCommand}"
IsRefreshing="{Binding IsRefreshing, Mode=OneWay}"
ItemAppearing="Handle_ItemAppearing"
ItemTapped="OnEmployerResultsListViewItemTapped">
<ListView.ItemTemplate>
<DataTemplate>
<local:EmployerResultViewCell />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
XAML.CS
private void Handle_ItemAppearing(object sender, ItemVisibilityEventArgs e)
{
var itemTypeObject = e.Item as EmployerProfile;
if (_viewModel.EmployerResults.Last() == itemTypeObject && _viewModel.EmployerResults.Count() != 1)
{
if (_viewModel.LoadMoreCommand.CanExecute(null))
{
_viewModel.LoadMoreCommand.Execute(null);
}
}
}
ViewModel
public EmployerResultsViewModel()
{
LoadMoreCommand = new RelayCommand(LoadMoreEmployerResult, () => !IsBusy);
EmployerResults = new ObservableRangeCollection<EmployerProfile>();
}
public ObservableRangeCollection<EmployerProfile> EmployerResults { get; set; }
private async void LoadMoreEmployerResult()
{
IsBusy = true;
EmployerResults.AddRange((await _employerApiClient.GetMoreData(pagenumber)));
IsBusy = false;
}
From my understanding, your are trying to do Lazy Loading.
-First, you should set the recycling Strategy like this: CachingStrategy="RecycleElement" if you want acceptable performances as described here https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/listview/performance, then re-test the behavior of the ItemAppearing event.
-Then, it may be a good idea to use or to analyze an existing component that handles your need. For example: http://15mgm15.ghost.io/2017/11/28/implement-an-infinite-scrolling-listview-with-xamarin-forms/
What I did is add additional blank cell at the end on LoadMoreEmployerResult and call for load more on appearing on that. On load more, I remove that blank cell. This is the only way I feel that can resolve my issue.
private async void LoadMoreEmployerResult()
{
IsBusy = true;
if(EmployerResults.Last().Name == "")
EmployerResults.RemoveAt(EmployerResults.Count - 1);
List<EmployerProfile> currentPageList= await _employerApiClient.GetMoreData(pagenumber);
if(currentPageList.Count > 0)
{
EmployerResults.AddRange(currentPageList);
EmployerResults.Add(new EmployerProfile());
}
IsBusy = false;
}

UWP - Refresh ListView from a running async method

I have written a method which sends an UDP broadcast und recieves the results in a loop which are instantly written in to a List<String>. Since I'm using ReceiveAsync() it is running asynchronous.
My question is: how can I refresh the ListView in my UI each time the loop adds a string to my list. So I want the results to be displayed instantly on the screen as they appear in the list.
Code
do
{
UdpReceiveResult result = await sclient.ReceiveAsync();
ipList.Add(result.RemoteEndPoint.Address.ToString());
// after this step I want the ListView to get refreshed
} while (sclient.Available != 0);
XAML ListView Code
<ListView x:Name="lbIps" HorizontalAlignment="Left" Height="174"
Margin="450,151,0,0" VerticalAlignment="Top" Width="298" />
Code behind XAML
public async void btnBroadcast_Click(object sender, RoutedEventArgs e)
{
await ND2.run();
lbIps1.ItemsSource = ND2.ipList;
}
First of all you should bing the data to the ListView using data binding:
<ListView x:Name="lbIps" ItemsSource="{x:Bind IpList}" ... />
Now you have to actually create such property in the code-behind:
ObservableCollection<string> IpList => ND2.ipList;
Finally, change the type of ipList to ObservableCollection<string>. You no longer have to set the ItemsSource manually inside the btnBroadcast_Click method as it is bound directly to the ND2.ipList. Also, thanks to the fact that it is a ObservableCollection<string> any new items added will automatically be reflected in the UI.
**Note: ** Make sure, you don't create a new instance of the ipList, because the ListView would stay bound to the original instance. I presume ipList is a field or a property:
public ObservableCollection<string> ipLIst {get;} = new ObservableCollection<string>();
Now the property is initialized at the beginning and will not change. You can use Clear() method to remove all elements in the collection if necessary instead of setting a new instance.

Updating a Label during an ICommand Action

In WPF, using MVVM design, I've created a screen designed to load large numbers of logs into a ListView on the click of a button. Upon return, a label is updated to display the number of logs returned. This process can sometimes take a while. Our DA is in the process of optimizing things, but meanwhile I am required to make the following changes to indicate to the user that the search is running:
Display the mouse as a WaitCursor.
Update the label text to display "Searching...".
I have a class which implements ICommand and I have the WaitCursor working correctly. However, I cannot get the desired behavior for updating the label to display when the search is running. My current code:
MyScreen.xaml
<Button
Name="DisplayButton"
Content="Display Logs"
Command="{Binding DisplayLogsCommand}"
Margin="0,64,10,0"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Width="112"/>
<Label
Content="{Binding LogsShowingText}"
Margin="0,0,127,8"
Foreground="#FF3C3B3B"
HorizontalAlignment="Right"
Width="145" Height="24"
VerticalAlignment="Bottom"
HorizontalContentAlignment="Right"
FontSize="11"/>
MyScreenVM.cs
private Command displayLogsCommand;
private string logShowingText;
public Command DisplayLogsCommand
{
get
{
if (this.displayLogsCommand == null)
{
// Attempt 3 made here.
bool useWaitCursor = true;
Func<bool> canExecute = () => this.ValidateFields();
Action execute = () =>
{
/*
* TODO: Update this.LogsShowingText to read "Searching..."
*/
// Attempt 1 and 2 made here.
LogEntry[] entries = this.ClientConnection.GetLogs();
this.LogsShowingText = string.Format("Showing {0} Logs", entries.Length);
this.FilteredEntries = new ObservableCollection<LogEntry>(entries);
};
this.displayLogsCommand = new Command(useWaitCursor, canExecute, execute);
}
return this.displayLogsCommand;
}
}
public string LogsShowingText
{
get
{
return this.logsShowingText;
}
set
{
this.logsShowingText= value;
OnPropertyChanged("LogsShowingText");
}
}
Thus far the results and my associated failed attempts are below:
After logs are returned, the Label only reads "Searching...".
Application.Current.Dispatcher.Invoke(new Action(() => this.LogsShowingText = "Searching..."));
After logs are returned, the Label only reads "Showing N Logs".
this.LogsShowingText = string.Format("Searching...");
Before and during search, the Label reads "Searching...", then after logs are returned, Label reads "Showing N Logs". Same code as #2, different location.
I understand this probably has something to do with the UI being blocked until the Action completes, which clearly explains attempt 1 showing the last queued update to the label and attempt 2 showing the last hard coded update to the label. Attempt 3 almost works, but the Label should not be updated until the user has clicked the button to perform the search. How can I do this?
Since this is an expensive command, meaning the UI hangs while it processes, you should convert it to an async command.
public Command DisplayLogsCommand
{
get
{
if (this.displayLogsCommand == null)
{
bool useWaitCursor = true;
Func<bool> canExecute = () => this.ValidateFields();
Action execute = async () =>
{
this.LogsShowingText = "Searching";
// Attempt 1 and 2 made here.
LogEntry[] entries = await Task<LogEntry[]>.Run(() => this.ClientConnection.GetLogs());
this.LogsShowingText = string.Format("Showing {0} Logs", entries.Length);
this.FilteredEntries = new ObservableCollection<LogEntry>(entries);
};
this.displayLogsCommand = new Command(useWaitCursor, canExecute, execute);
}
return this.displayLogsCommand;
}
}
By making the Action delegate async, you can now await within the Action. This lets you wrap the call to your DataLayer in a Task and await it. Now that the expensive operation is running off the UI thread, your label will be updated properly before and after. No need to marshall the changes using a Dispatcher.
This will update the labels only when the command has been executed by the user clicking the button.

Multi-threading WPF on the UI

In my C# WPF application I have a DataGrid and right above it there is a TextBox for the user to search and filter the grid as they type. If the user types fast though, no text will appear until 2 seconds after they type because the UI thread is too busy updating the grid.
Since most of the delay is all on the UI side (i.e. filtering the datasource is nearly instant, but rebinding and re-rendering the grid is slow), multi-threading has not been helpful. I then tried setting the dispatcher of just the grid to be at a lower level while the grid gets updated, but this didn't solve the issue either. Here's some code... Any suggestions on how to solve this type of problem?
string strSearchQuery = txtFindCompany.Text.Trim();
this.dgCompanies.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(delegate
{
//filter data source, then
dgCompanies.ItemsSource = oFilteredCompanies;
}));
Using a ListCollectionView as your ItemsSource for the grid and updating the Filter works much faster than re-assigning the ItemsSource.
The example below filters 100000 rows with no apparent lag by simply refreshing the View in the setter for the search term text property.
ViewModel
class ViewModel
{
private List<string> _collection = new List<string>();
private string _searchTerm;
public ListCollectionView ValuesView { get; set; }
public string SearchTerm
{
get
{
return _searchTerm;
}
set
{
_searchTerm = value;
ValuesView.Refresh();
}
}
public ViewModel()
{
_collection.AddRange(Enumerable.Range(0, 100000).Select(p => Guid.NewGuid().ToString()));
ValuesView = new ListCollectionView(_collection);
ValuesView.Filter = o =>
{
var listValue = (string)o;
return string.IsNullOrEmpty(_searchTerm) || listValue.Contains(_searchTerm);
};
}
}
View
<TextBox Grid.Row="0" Text="{Binding SearchTerm, UpdateSourceTrigger=PropertyChanged}" />
<ListBox ItemsSource="{Binding ValuesView}"
Grid.Row="1" />
If you are targeting .net 4.5, an option is to set the Delay property on your TextBox which will prevent setting the source value until a certain time threshold is met (until the user stops typing).
<TextBox Text="{Binding SearchText, Delay=1000}"/>
This waits for 1 second after there is no user input to set the source value.
Another option is to have a button trigger your filter/search instead of when the textbox changes.

Categories

Resources