Unexpected result with grouped AutoSuggestBox on mobile - c#

I'm using an AutoSuggestBox with grouping support using the style found here, but I'm having a weird issue in how the suggestion popup is shown on mobile (on desktop is 100% fine, I can see the grouped list inside the popup).
What happens is that, while I'm writing in the AutoSuggestBox, an ungrouped popup with wrong positioning is shown, as you can see here:
This lasts until I select one of the items, but the selection provides even weirder results by showing the correct popup, even if it's filtered with the text that I had before selecting the item.
You can see the correct behavior on desktop here:
<AutoSuggestBox Style="{StaticResource GroupedAutoSuggestBoxStyle}"
UpdateTextOnSelect="True"
TextMemberPath="Name"
VerticalAlignment="Center"
ItemsSource="{x:Bind ServicesCollectionViewSource.View, Mode=OneWay}"
TextChanged="ServicesFilterAutoSuggestBox_OnTextChanged"
QuerySubmitted="ServicesFilterAutoSuggestBox_OnQuerySubmitted"
LostFocus="ServicesFilterAutoSuggestBox_OnLostFocus"
AutoMaximizeSuggestionArea="True"
Grid.Column="1">
...
private void ServicesFilterAutoSuggestBox_OnTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if (args.Reason != AutoSuggestionBoxTextChangeReason.UserInput) return;
if (string.IsNullOrEmpty(sender.Text) && ViewModel.AccountService != null)
{
// User canceled previous selection, so we reset it
ViewModel.AccountService = null;
}
// Filter the results
ServicesCollectionViewSource.Source = from services
in ViewModel.Services
where services.Name.ToLowerInvariant().Contains(sender.Text.ToLowerInvariant())
group services by services.Section.Id;
}
private void ServicesFilterAutoSuggestBox_OnQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
if (args.ChosenSuggestion != null)
{
ViewModel.AccountService = (Service) args.ChosenSuggestion;
}
else
{
ViewModel.AccountService = null;
}
}
I don't understand why this works fine on desktop while it has this weird behavior on mobile, and I really need your help.

Related

Xamarin Picker showing undesirable elements on UI

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();
}

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;
}

IDataErrorInfo not updated

I encountered a problem with the IDataErrorInfo Interface and a wizard I'm currently programming.
The intention of my programm is to ask some Inputs ( usually done with a barcode scanner) and depending on the inputs start a specific sequence.
This is working as intendet. To make sure to catch wrong scans all inputs are check with an event ( OnValueParseFailed) If this event is triggered my current textbox is focused and all text selected:
this.MyWizardViewModel.ValueParseFailed += (s, e) =>
{
switch (e.Parameter)
{
case "ProductionOrder":
this.TextBoxProduction.Focus();
this.TextBoxProduction.SelectAll();
break;
The Interface itself is included this way:
public string this[string name]
{
get
{
string result = null;
if ((name == "ProductionOrder") && (!string.IsNullOrEmpty(this.ProductionOrder)))
{
if (this.System.FirmwareVersion == 0)
result = Lang.Strings.WrongEntry;
}
Its working for the first run. But if the wizard is finished or aborted and run a second time without closing the app, no error message is shown.
The Reset simply returns the app to default values.
public void ResetApplikation()
{
this.System.Clear(); // reset System values
this.ProductionOrder = string.Empty;
this.BmsTypeCode = string.Empty;
this.CellStack1TypeCode = string.Empty;
this.CellClass1 = string.Empty;
this.CellStack2TypeCode = string.Empty;
this.CellClass2 = string.Empty;
this.IsSystemProgrammed = false;
this.IsSystemParameterized = false;
this.MyMachine.Abort(); // reset wizard state
}
While debugging I can see the Interface to be handeled correctly. But no error is displayed.
In XAML the binding is set TwoWay
<TextBox Name="TextBoxProduction" Grid.Row="2" Width="200" Margin="10"
Style="{StaticResource TextBoxNormal}" Loaded="TextBoxProduction_Loaded"
Text="{Binding Path=ProductionOrder, ValidatesOnDataErrors=True,
NotifyOnValidationError=True, Delay=100,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
I'm using MahApps but as the textbox class is based on the wpf textbox I doubt a bug in this element is the problem. Any suggestions would be great.
Thank you.
The Answer of Domysee helped me.
Implementing INotifyDataErrorInfo instead of IDataErrorInfo was a major change but it fixed the problem!

Windows Store App: How to make ListView with expandable/enlargeable ListItems?

I have a Listview with items, in a C# Windows Store App (is that what you call these? I heard they're not called Metro Apps anymore).
Similar to the ExpandableListView in Android, I want to be able to tap on listitems (not the buttons) for that listitem to expand, tap on the expanded listitem for it to collapse, and if you tap on another listitem, the currently expanded listitem will collapse and the other will expand.
In my particular case I have a DataTemplate for both the expanded and non-expanded view of the listitems. I've seen that Android's ExpandableListView can expand the listitem with additional information (the Expander from WPF does something similar to that), instead of replacing it with a larger item, but is there a common solution for this in Windows Store Apps?
If not, what is the closest equivalent?
Like on the following drawing, I want to know if there is a component that can expand listitems in this way, or if not, which alternatives I have:
I ended up with a solution that works but doesn't look too fancy. It switches DataTemplate when you click items but there's no animation: it switches instantly.
Here's the important code parts:
XAML
<Page.Resources>
<DataTemplate x:Key="dtSmall">
<!--Component template for the un-expanded listitems-->
</DataTemplate>
<DataTemplate x:Key="dtEnlarged">
<!--Component template for the expanded listitems-->
</DataTemplate>
</Page.Resources>
<Grid>
<ListView x:Name="lvEnlargeable"
IsItemClickEnabled="True"
ItemTemplate="{StaticResource dtSmall}"
ItemsSource="{Binding ...}"
SelectionChanged="LVEnlargeable_SelectionChanged"
ItemClick="LVEnlargeable_ItemClick"/>
</Grid>
XAML.CS
public sealed partial class MainPage : Page
{
private DataTemplate dtSmall;
private DataTemplate dtEnlarged;
public MainPage()
{
this.InitializeComponent();
dtSmall = (DataTemplate)Resources["dtSmall"];
dtEnlarged = (DataTemplate)Resources["dtEnlarged"];
}
// A selected item is treated as an expanded/enlarged item
private void LVEnlargeable_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
/* First we set all the items that has been deselected
to be collapsed, aka. using the dtSmall DataTemplate.
We expect 0 or 1 item to have been deselected
but handle all cases easily with a foreach loop.
*/
foreach (var item in e.RemovedItems)
{
// Set the DataTemplate of the deselected ListViewItems
((ListViewItem)(sender as ListView).ContainerFromItem(item)).ContentTemplate = dtSmall;
}
/* Then we set all the items that has been selected
to be expanded.
We should probably throw an Exception if more than 1 was found,
because it's unwanted behavior, but we'll ignore that for now.
*/
foreach (var item in e.AddedItems)
{
((ListViewItem)(sender as ListView).ContainerFromItem(e.AddedItems[0])).ContentTemplate = dtEnlarged;
}
}
/* We need click events because SelectionChanged-events
cannot detect clicks on an already selected item */
private void LVEnlargeable_ItemClick(object sender, ItemClickEventArgs e)
{
ListView lv = (sender as ListView);
/* Having set the IsItemClickEnabled property on the ListView to True
we have to handle selection events manually.
If nothing is selected when this click occurs, then select this item*/
if (lv.SelectedItem == null)
{
lv.SelectedItem = e.ClickedItem;
}
else
{
// Clicking on an expanded/selected/enlarged item will deselect it
if (lv.SelectedItem.Equals(e.ClickedItem))
{
lv.SelectedItem = null;
}
else
{ /* If it's not a selected item, then select it
(and let SelectionChanged unselect the already selected item) */
lv.SelectedItem = e.ClickedItem;
}
}
}
}
I haven't tested if this isolated code is enough, on its own, for this solution, but I hope it is, and this code at least contain the key points. It's late and I just wanted to post something for the curious-minded people. If this shows not to work for you, then please leave a comment about the issue and I'll make sure to add the missing parts.
I also messed with the ListViewItemStyleContainer's ListViewItemPresenter to have better selection effects etc. but I figure it's best to keep it short. If you find this interesting as well, then feel free to leave a comment for that too, and I'll try include it.

Drag&Drop in a WPF TreeView on the Scrollbar

we're using the MVVM pattern in our application and in a window, we have two TreeViews allowing to drag items from the first and drop it on the second tree. To avoid code behind, we're using behaviours to bind the drag and drop against the ViewModel.
The behaviour is implemented pretty much like this example and working like a charm, with one bug.
The scenario is a tree which is bigger than the window displaying it, therefore it has a vertical scroll bar. When an item is selected and the user wants to scroll, the program starts drag and drop (which prevents the actual scrolling and therefore isn't what we want).
This isn't very surprising as the scrollbar is contained in the TreeView control. But I'm unable to determine safely if the mouse is over the scrollbar or not.
The TreeViewItems are represented by a theme using Borders, Panels and so on, so a simple InputHitTest isn't as simple as one may think.
Has anybody already encountered the same problem?
If more code coverage of the problem is required, I can paste some lines from the .xaml.
Edit
Incorporating Nikolays link I solved the problem using a IsMouseOverScrollbar method, if anyone has this problem in the future the code from above must be altered in the following way:
private static void PreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed || startPoint == null)
return;
if (!HasMouseMovedFarEnough(e))
return;
if (IsMouseOverScrollbar(sender, e.GetPosition(sender as IInputElement)))
{
startPoint = null;
return;
}
var dependencyObject = (FrameworkElement)sender;
var dataContext = dependencyObject.GetValue(FrameworkElement.DataContextProperty);
var dragSource = GetDragSource(dependencyObject);
if (dragSource.GetDragEffects(dataContext) == DragDropEffects.None)
return;
DragDrop.DoDragDrop(
dependencyObject, dragSource.GetData(dataContext), dragSource.GetDragEffects(dataContext));
}
private static bool IsMouseOverScrollbar(object sender, Point mousePosition)
{
if (sender is Visual)
{
HitTestResult hit = VisualTreeHelper.HitTest(sender as Visual, mousePosition);
if (hit == null) return false;
DependencyObject dObj = hit.VisualHit;
while(dObj != null)
{
if (dObj is ScrollBar) return true;
if ((dObj is Visual) || (dObj is Visual3D)) dObj = VisualTreeHelper.GetParent(dObj);
else dObj = LogicalTreeHelper.GetParent(dObj);
}
}
return false;
}
Take a look at this implementation of Drag and Drop behaviour for ListView by Josh Smith. It has code to deal with scrollbars and some other unobvious problems of DnD (like drag treshold, precise mouse coordinates and such). This behaviour can be easily adopted to work with TreeViews too.
I had the same Problem. I solved it by placing the TreeView inside a ScrollViewer.
<ScrollViewer Grid.Column="0">
<TreeView BorderThickness="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MouseMove="DeviceTree_OnMouseMove" PreviewMouseLeftButtonDown="DeviceTree_OnPreviewMouseLeftButtonDown" Name="DeviceTree" ItemsSource="{Binding Devices}"/>
</ScrollViewer>

Categories

Resources