Binding a WPF DataGridComboBoxColumn with MVVM

I've looked at the answers to various questions, but haven't managed to map the content in the answers to the problem I'm attempting to solve. I've reduced it down to the following code (representative of the outcome I'm trying to achieve), and basically want to be able to render the Person.TitleId as its corresponding Title.TitleText when the row isn't being edited, and have the drop-down bound correctly so that it displays the TitleTexts in the drop-down and writes the associated TitleId back to the Person record when its changed.

In short, what do I put in my to achieve this?

App.xaml.cs

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    var viewModel = new ViewModels.MainWindowViewModel();
    var mainWindow = new MainWindow();
    mainWindow.DataContext = viewModel;
    mainWindow.ShowDialog();
}

MainWindow.xaml


    
        
            
                
                    
                
                
                    
                
            
        
    

Person.cs

public class Person
{
    public int TitleId { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Title.cs

public struct Title
{
    public Title(int titleId, string titleText)
        : this()
    {
        TitleId = titleId;
        TitleText = titleText;
    }

    public string TitleText { get; private set; }
    public int TitleId { get; private set; }

    public static List GetAvailableTitles()
    {
        var titles = new List<Title>();

        titles.Add(new Title(1, "Mr"));
        titles.Add(new Title(2, "Miss"));
        titles.Add(new Title(3, "Mrs"));

        return titles;
    }
}
</code></pre><p>MainWindowViewModel.cs</p><pre><code>public class MainWindowViewModel : ViewModelBase
{
    private ObservableCollection<Person> contacts;
    private List<Title> titles;

    public MainWindowViewModel()
    {
        titles = Title.GetAvailableTitles();

        Contacts = new ObservableCollection<Person>();
        Contacts.Add(new Person() { FirstName = "Jane", LastName = "Smith", TitleId = 2 });
    }

    public List<Title> Titles
    {
        get { return titles; }
    }

    public ObservableCollection<Person> Contacts
    {
        get { return contacts; }
        set
        {
            if (contacts != value)
            {
                contacts = value;
                this.OnPropertyChanged("Contacts");
            }
        }
    }
}
</code></pre><p>ViewModelBase.cs</p><pre><code>public class ViewModelBase : INotifyPropertyChanged
{
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}
</code></pre></p>
         </div>
         <div class="votes-question">
            <div class="vote-count" itemprop="upvoteCount">26</div><i class="fa fa-thumbs-o-up"></i>
         </div>
         <div class="tags">
            <a href="/questions/tagged/wpf" class="tag"  title="wpf" rel="tag">wpf</a> <a href="/questions/tagged/mvvm" class="tag"  title="mvvm" rel="tag">mvvm</a> <a href="/questions/tagged/datagrid" class="tag"  title="datagrid" rel="tag">datagrid</a> <a href="/questions/tagged/datagridcomboboxcolumn" class="tag"  title="datagridcomboboxcolumn" rel="tag">datagridcomboboxcolumn</a>         </div>
         <div class="clearfix"></div>
         <div class="action-time">
            задан Community            <span title="23 May 2017 в 12:32 ">23 May 2017 в 12:32 </span>
         </div>
         
         <a class="s-link" href="/questions/279051/binding-a-wpf-datagridcomboboxcolumn-with-mvvm" title="поделиться">поделиться</a>
      </div>
   </div>
  <div style="height:100px;margin:10px 0px;" class="">

    <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> <!-- siteask before post --> <ins class="adsbygoogle"      style="display:block;height:100px"
                                                                                                                             data-ad-client="ca-pub-2355906945027976"
                                                                                                                             data-ad-slot="" data-ad-format="auto"></ins>
    <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script>

    </div>
   <div class="answers" id="answers">
   
      <h2 class="pull-left"><span itemprop="answerCount">0</span> ответов</h2>
      <div class="clearfix"></div>

      <div class="answer-pager">
         <div class="pagination">
                     </div>
      </div>

            <div style="margin-top: 20px;">
          Другие вопросы по тегам:          <div class="tags" style="display: inline-block; float: none;">
         <a href="/questions/tagged/wpf" class="tag"  title="wpf" rel="tag">wpf</a> <a href="/questions/tagged/mvvm" class="tag"  title="mvvm" rel="tag">mvvm</a> <a href="/questions/tagged/datagrid" class="tag"  title="datagrid" rel="tag">datagrid</a> <a href="/questions/tagged/datagridcomboboxcolumn" class="tag"  title="datagridcomboboxcolumn" rel="tag">datagridcomboboxcolumn</a>       </div>
        <h3 class="m-t-20">Похожие вопросы:</h3>

        <div class="related-block">
          <ul>
                          <li><div class='votes-answer green'><span class='vote-count'>33</span> <i class="fa fa-thumbs-o-up"></i></div> <a href="/questions/121366/pri-sozdanii-novogo-gui-dejstvitelno-li-wpf-javljaetsja-predpochtitelnym-variantom-po-windows-forms-zakrytyj" title="При создании нового GUI действительно ли WPF является предпочтительным вариантом по Windows Forms? [закрытый]">При создании нового GUI действительно ли WPF является предпочтительным вариантом по Windows Forms? [закрытый]</a> - 25 September 2008 11:15 </li>
                            <li><div class='votes-answer green'><span class='vote-count'>30</span> <i class="fa fa-thumbs-o-up"></i></div> <a href="/questions/2934/dostup-k-upravleniyu-v-kode-pozadi-rez-ourcedictionary-duplicate" title="Доступ к управлению в коде позади Resourcedictionary [duplicate] ">Доступ к управлению в коде позади Resourcedictionary [duplicate] </a> - 3 September 2017 16:06 </li>
                            <li><div class='votes-answer green'><span class='vote-count'>30</span> <i class="fa fa-thumbs-o-up"></i></div> <a href="/questions/2936/nullreferenceexception-v-getippropertiez-unicaz-taddrez-z-ez-firz-tordefault-duplicate" title="NullReferenceException в GetIPProperties (). UnicastAddresses.FirstOrDefault () [duplicate] ">NullReferenceException в GetIPProperties (). UnicastAddresses.FirstOrDefault () [duplicate] </a> - 3 September 2017 16:06 </li>
                            <li><div class='votes-answer green'><span class='vote-count'>29</span> <i class="fa fa-thumbs-o-up"></i></div> <a href="/questions/14575/est-li-sposob-vy-zvat-metod-v-nabore-svojstv-auto-dublikat" title="Есть ли способ вызвать метод в наборе свойств auto?  [Дубликат] ">Есть ли способ вызвать метод в наборе свойств auto?  [Дубликат] </a> - 9 April 2013 14:04 </li>
                            <li><div class='votes-answer green'><span class='vote-count'>23</span> <i class="fa fa-thumbs-o-up"></i></div> <a href="/questions/14595/otobrazit-tekst-po-umolchaniyu-dlya-combobox-wpf-duplicate" title="отобразить текст по умолчанию для combobox wpf [duplicate] ">отобразить текст по умолчанию для combobox wpf [duplicate] </a> - 29 April 2013 14:55 </li>
                            <li><div class='votes-answer green'><span class='vote-count'>22</span> <i class="fa fa-thumbs-o-up"></i></div> <a href="/questions/128983/kakoj-realnyj-mir-prilozhenija-wpf-tam-zakrytyj" title="Какой реальный мир приложения WPF там? [закрытый]">Какой реальный мир приложения WPF там? [закрытый]</a> - 3 July 2013 03:57 </li>
                            <li><div class='votes-answer green'><span class='vote-count'>22</span> <i class="fa fa-thumbs-o-up"></i></div> <a href="/questions/193276/skrytye-funkcii-wpf-i-xaml" title="Скрытые функции WPF и XAML?">Скрытые функции WPF и XAML?</a> - 25 September 2017 20:52 </li>
                          </ul>
        </div>

      </div>
   </div>
   
</div>      </div>
      <aside class="sidebar">
        <div class="awrap">

<script async src="https://yastatic.net/pcode-native/loaders/loader.js"></script>
<script>
    (yaads = window.yaads || []).push({
        id: "553274-2",
        render: "#id-553274-2"
    });
</script>
<div id="id-553274-2"></div>
          <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<ins class="adsbygoogle"
     style="display:inline-block;width:300px;height:600px"
     data-ad-client="ca-pub-2355906945027976"
     data-ad-slot="8038370725"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>


        </div>
      </aside>

    </div>
  </div>
  <footer class="footer">
    <div class="wrapper wrapper--sm">
      <div class="footer-navs-col">
        <div class="footer-nav footer-nav--menu">

          <div class="footer-coryright">© 2017 - 2020 Вопросы и ответы по программированию</div>
        </div>
        <div class="footer-nav footer-nav--catalog">
        </div>
      </div>
      <div class="footer-contacts-col">
        <div class="soc-widget-col">
        </div>
      </div>
      <div class="clearfix"></div>
    </div>

  </footer>

</div>

<script type="text/javascript" src="/js/ui/jquery-ui-1.8.16.custom.min.js"></script>
<script type="text/javascript" src="/js/ui/external/jquery.cookie.js"></script>

<script type="text/javascript" src="/js/versions/menu.ru.u1607887878.js"></script>


<script type="text/javascript" src="/js/jquery.fancybox.min.js"></script>
<script type="text/javascript" src="/js/slick.min.js"></script>
<script type="text/javascript" src="/js/jquery.maskedinput.min.js"></script>

<script type="text/javascript" src="/js/versions/scripts.ru.u1607887878.js"></script>


<!-- Yandex.Metrika counter --> <script type="text/javascript" > (function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)}; var z = null;m[i].l=1*new Date(); for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }} k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)}) (window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym"); ym(90030325, "init", { clickmap:true, trackLinks:true, accurateTrackBounce:true, webvisor:true }); </script> <noscript><div><img src="https://mc.yandex.ru/watch/90030325" style="position:absolute; left:-9999px;" alt="" /></div></noscript> <!-- /Yandex.Metrika counter -->


<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-123993370-1"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'UA-123993370-1');
</script>

</div>
<script type="application/ld+json">
  {
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "Программирование - вопросы и ответы",
  "alternateName": "Программирование - вопросы и ответы",
  "url": "https://legkovopros.ru",
  "potentialAction": {
     "@type": "SearchAction",
     "target": "https://legkovopros.ru/search?search={search_term_string}",
     "query-input": "required name=search_term_string"
   }
}
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "Программирование - вопросы и ответы",
  "url": "https://legkovopros.ru",
  "logo": "https://legkovopros.ru/i/logo.png",
  "email": "info@legkovopros.ru",
   "telephone": ""

}




</script>
</body>
</html>