John Ernest
John Ernest

Reputation: 807

SelectedIndex on ComboBox to first item on data change

I'm learning UWP in VS2015 Community right now and having trouble with one section in regards to a ComboBox and could really use some help.

I'm writing a Bible app and have 3 ComboBoxes for Translation, Book, and Chapter. When I change the Book dropdown it should change the Chapter to 1. At least until I make a forward and back button for chapters, just covering the basics right now. When I change the Translation let's say from NIV to KJV it should change to the currently selected Book/Chapter in that translation.

I've preloaded the texts from XML and loaded them into an object called dataLoader. I'm doing selections on it via LINQ in the code below.

So right now I say something like:

private void DataLoader_Completed(object sender, EventArgs e)
{
    dataLoaded = true;
    cmb_Translation.ItemsSource = from t in dataLoader.Translations select new { t.TranslationShortName };
    cmb_Book.ItemsSource = from b in dataLoader.Translations[0].Books select new { b.BookName };
    cmb_Chapter.ItemsSource = from c in dataLoader.Translations[0].Books[0].Chapters select new { c.Index };
    cmb_Book.SelectedIndex = 0;
    cmb_Translation.SelectedIndex = 0;
    cmb_Chapter.SelectedIndex = 0;
}

private void translationChanged()
{
    chapterChanged();
}

private void bookChanged()
{
    cmb_Chapter.ItemsSource = from c in dataLoader.Translations[cmb_Translation.SelectedIndex].Books[cmb_Book.SelectedIndex].Chapters select new { c.Index };
    cmb_Chapter.SelectedIndex = 0;
}

private void chapterChanged()
{
    textBlock_Verses.Text = dataLoader.Translations[cmb_Translation.SelectedIndex].Books[cmb_Book.SelectedIndex].Chapters[cmb_Chapter.SelectedIndex].TextLineSeparated;
}

private void cmb_Translation_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    translationChanged();
}

private void cmb_Book_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    bookChanged();
}

private void cmb_Chapter_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    chapterChanged();
}

I'm getting errors back though on the first run that the index is out of range because at first the SelectedIndex of the translation is -1, if I run translation first it will give me an out of range on the book for SelectedIndex being -1.

I want the selected index changing to trigger the proper events but as you can see that's not going to work how it is now. Also the code is pretty messy, I've started looking a bit into Binding but there are a lot of hurdles like figuring out how to bind to a property that returns a LINQ result. I'm not sure how to move forward on this and definitely appreciate any help I can get.

Upvotes: 1

Views: 108

Answers (2)

John Ernest
John Ernest

Reputation: 807

Here's the solution I came to that works, although I think maybe a better solution could have been achieved using Binding to properties I've created, some feedback on improvements could always be helpful from a design perspective. What I did essentially was create an UpdateChapterText Boolean property that when set to false won't process my SelectedIndex change events, but will still allow me to update underlying data sources and change the SelectedIndex on the fly, that way I can process events in one go, otherwise it might be fire multiple events to update the underlying data sources. I needed to be able to do this because for the chapter text view it relies on both the translation and book selected indexes to be set, but the default behavior only allows one or the other to be set before events process, which becomes unsolvable at least I think for now unless you turn off events processing I found.

    /// <summary>
    /// This is a helper property for setting the Translation SelectedIndex
    /// </summary>
    private int TranslationIndex
    {
        get
        {
            return cmb_Translation.SelectedIndex;
        }
        set
        {
            cmb_Translation.SelectedIndex = value;
        }
    }

    /// <summary>
    /// This is a helper property for setting the Book SelectedIndex
    /// </summary>
    private int BookIndex
    {
        get
        {
            return cmb_Book.SelectedIndex;
        }
        set
        {
            cmb_Book.SelectedIndex = value;
        }
    }

    /// <summary>
    /// This is a helper property for setting the Chapter SelectedIndex
    /// </summary>
    private int ChapterIndex
    {
        get
        {
            return cmb_Chapter.SelectedIndex;
        }
        set
        {
            cmb_Chapter.SelectedIndex = value;
        }
    }

    /// <summary>
    /// Retrieves the currently selected Chapter listing
    /// </summary>
    public IEnumerable<ChapterIndex> CurrentChapters
    {
        get
        {
            return from c in dataLoader.Translations[TranslationIndex].Books[BookIndex].Chapters select new ChapterIndex { Index = c.Index };
        }
    }

    /// <summary>
    /// Retrieves Genesis in the first loaded translation
    /// </summary>
    public IEnumerable<ChapterIndex> Genesis
    {
        get
        {
            return from c in dataLoader.Translations[0].Books[0].Chapters select new ChapterIndex { Index = c.Index };
        }
    }

    public IEnumerable<BookNames> CurrentBooks
    {
        get
        {
            return from b in dataLoader.Translations[TranslationIndex].Books select new BookNames { BookName = b.BookName };
        }
    }

    /// <summary>
    /// Allows events to process on ComboBoxes and Back/Forward Buttons
    /// to change Chapters, you usually don't want to do this lots of
    /// times in one second if changing the Translation/Book/Chapter
    /// all at one time so you may set it to false first, update your
    /// variables, and then set it back to true so updating events will
    /// process correctly.
    /// </summary>
    public bool UpdateChapterText { get; set; }

    /// <summary>
    /// The DataLoader object loads up the various Bible translation texts
    /// </summary>
    TheBible.Model.DataLoader.DataLoader dataLoader;

    public MainPage()
    {
        this.InitializeComponent();
        dataLoader = new Model.DataLoader.DataLoader();
        dataLoader.Completed += DataLoader_Completed;
        ApplicationView.GetForCurrentView().TryEnterFullScreenMode();
    }

    private void DataLoader_Completed(object sender, EventArgs e)
    {
        UpdateChapterText = false;
        cmb_Translation.ItemsSource = from t in dataLoader.Translations select new { t.TranslationShortName };
        cmb_Book.ItemsSource = from b in dataLoader.Translations[0].Books select new { b.BookName };
        cmb_Chapter.ItemsSource = Genesis;
        cmb_Translation.SelectedIndex = 0;
        cmb_Book.SelectedIndex = 0;
        UpdateChapterText = true;
        cmb_Chapter.SelectedIndex = 0;
    }

    private void translationChanged()
    {
        chapterChanged();
    }

    private void bookChanged()
    {
        UpdateChapterText = false;
        cmb_Chapter.ItemsSource = CurrentChapters;
        UpdateChapterText = true;
        cmb_Chapter.SelectedIndex = 0;
    }

    private void chapterChanged()
    {
        textBlock_Verses.Text = dataLoader.Translations[TranslationIndex].Books[BookIndex].Chapters[ChapterIndex].TextLineSeparated;
    }

    private void decrementChapter()
    {
        UpdateChapterText = false;
        if (this.cmb_Chapter.SelectedIndex == 0)
        {
            if (this.cmb_Book.SelectedIndex > 0)
            {
                this.cmb_Book.SelectedIndex--;
                UpdateChapterText = true;
                this.cmb_Chapter.SelectedIndex = CurrentChapters.Count() - 1;
            }
        }
        else
        {
            UpdateChapterText = true;
            this.cmb_Chapter.SelectedIndex--;
        }
        UpdateChapterText = true;
    }

    private void incrementChapter()
    {
        UpdateChapterText = false;
        if (this.cmb_Chapter.SelectedIndex == this.cmb_Chapter.Items.Count - 1)
        {
            if (this.cmb_Book.SelectedIndex < this.cmb_Book.Items.Count - 1)
            {
                this.cmb_Book.SelectedIndex++;
                UpdateChapterText = true;
                this.cmb_Chapter.SelectedIndex = 0;
            }
        }
        else
        {
            UpdateChapterText = true;
            this.cmb_Chapter.SelectedIndex++;
        }
        UpdateChapterText = true;
    }

    private void cmb_Translation_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (UpdateChapterText)
            translationChanged();
    }

    private void cmb_Book_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (UpdateChapterText)
            bookChanged();
    }

    private void cmb_Chapter_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (UpdateChapterText)
            chapterChanged();
    }

    private void btn_Forward_Click(object sender, RoutedEventArgs e)
    {
        incrementChapter();
    }

    private void btn_Back_Click(object sender, RoutedEventArgs e)
    {
        decrementChapter();
    }

    private void btn_FullScreen_Click(object sender, RoutedEventArgs e)
    {
        var view = ApplicationView.GetForCurrentView();
        if (view.IsFullScreenMode)
        {
            sym_FullScreen.Symbol = Symbol.FullScreen;
            view.ExitFullScreenMode();
        }
        else
        {
            sym_FullScreen.Symbol = Symbol.BackToWindow;
            view.TryEnterFullScreenMode();
        }
    }

Upvotes: 0

Romasz
Romasz

Reputation: 29792

Combobox can have no selection - selected item is null and this is how it's initialized, so before you set SelectedInexes all are null (this means that SelectedIndex == -1):

private void DataLoader_Completed(object sender, EventArgs e)
{
    dataLoaded = true;
    cmb_Translation.ItemsSource = from t in dataLoader.Translations select new { t.TranslationShortName };
    cmb_Book.ItemsSource = from b in dataLoader.Translations[0].Books select new { b.BookName };
    cmb_Chapter.ItemsSource = from c in dataLoader.Translations[0].Books[0].Chapters select new { c.Index };
    cmb_Book.SelectedIndex = 0; // <- you set here selected index for book
                                //    which fires bookchanged even right away
                                //    In that event cmb_Translation.SelectedIndex 
                                //    is still -1 which will surely throw exception
    cmb_Translation.SelectedIndex = 0;
    cmb_Chapter.SelectedIndex = 0;
}

You probably should put some check-ups if values are properly set before using them. Also think if there is a chance when there is no selection state.

private void bookChanged()
{
    if (cmb_Translation.SelectedIndex >= 0)
    {
        cmb_Chapter.ItemsSource = from c in dataLoader.Translations[cmb_Translation.SelectedIndex].Books[cmb_Book.SelectedIndex].Chapters select new { c.Index };
        cmb_Chapter.SelectedIndex = 0;
    }
}

There is one hiccup with this - you will have to launch bookchanged() at the endo of DataLoader_Completed manually, as it won't process before, due to -1.

Upvotes: 1

Related Questions