Search code examples
c#xamarinxamarin.formsmodal-dialogpicker

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:

Initial screen, without title. Line appears after first click, without title. After clicking again the line stays and the modal appears, without title.

With the title, I click the Existing button, the line and title show, click it again and the modal appears:

Initial screen, with title. Line appears after first click, with title. After clicking again the line stays and the modal appears, with title.

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.


Solution

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