ARTICLE AD BOX
I'm creating an Avalonia app, guided by the MVVM Community Toolkit practices. One function of the app involves opening a dialog which contains a DataGrid showing a list of language keys and translations in five languages. This data is loaded from a JSON file and each file may not have the same languages, so all but the first column of the DataGrid are generated at load time. The user then selects a row and its data is used by the main viewmodel after the dialog is closed.
My problem is, while the data context for the dialog and its DataGrid appears to be properly populated with the list in an ObservableCollection and even registers a selected row, the DataGrid itself is never visible. I'm doing a lot of the same things that a previous app did, the largest difference being that the DataGrid was on the main window instead of a dialog.
As a mechanical summary, clicking the button on the main window commands the main viewmodel to send a message to the main window code-behind to open the dialog. The dialog's data context is initialized with the translations received from the main view model, which keeps the list after loading it from the file. The dialog is ordered to populate the language columns in its DataGrid, then opened. Once the dialogue is closed, the main viewmodel hears the code-behind's reply that contains the data it needs.
Packages used:
Translations.json:
[ { "Key": "LangOrder", "Xl8ns": [ "en-US", "fr-FR", "es-ES", "de-DE", "it-IT" ] }, { "Key": "LangNames", "Xl8ns": [ "English", "Français", "Español", "Deutsch", "Italiano" ] }, { "Key": "AccumulationArea", "Xl8ns": [ "Accumulation Area", "Zone d'accumulation", "Zona de acumulación", "Beschleunigungsbereich", "Area di accumulo" ] }, { "Key": "Color", "Xl8ns": [ "Color", "Couleur", "Color", "Farbe", "Colore" ] }, { "Key": "Quality", "Xl8ns": [ "Quality", "Qualité", "Calidad", "Qualität", "Qualità" ] } ]MainWindow.axaml:
<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="using:WhereIsMyDlgDataGrid.ViewModels" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="WhereIsMyDlgDataGrid.Views.MainWindow" x:DataType="vm:MainWindowViewModel" Icon="/Assets/avalonia-logo.ico" WindowState="Maximized" Title="WhereIsMyDlgDataGrid"> <Design.DataContext> <vm:MainWindowViewModel/> </Design.DataContext> <StackPanel> <Button Content="Open Dialog w DataGrid" HorizontalAlignment="Center" VerticalAlignment="Center" Command="{Binding PickLanguageKeyCommand}" /> <TextBlock Text="Picked language key:" /> <TextBox Text="{Binding PickedLangKey}" /> </StackPanel> </Window>MainWindow.axaml.cs (after usings and namespace):
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); WeakReferenceMessenger.Default.Register(this, OpenPickLanguageKeyDialog()); } private static MessageHandler<MainWindow, PickLanguageKeyMessage> OpenPickLanguageKeyDialog() { return static (w, m) => { var dlgVM = new PickLanguageKeyResultVM(m.Translations, m.CurrentKey); var dialog = new TranslationsGridDlg { Title = "Pick Language Key", DataContext = dlgVM }; dialog.InitLanguageColumns(dlgVM.Translations[0]); // Show dialog window and reply with returned PickLanguageKeyResultVM or null when the dialog is closed m.Reply(dialog.ShowDialog<PickLanguageKeyResultVM?>(w)); }; } }MainWindowViewModel.cs:
public partial class MainWindowViewModel : ViewModelBase { [ObservableProperty] private string _pickedLangKey = "PickSomething"; private List<TranslationKey> _translations = []; public MainWindowViewModel() { var transJSON = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\Translations.json")); _translations = JsonConvert.DeserializeObject<List<TranslationKey>>(transJSON); } [RelayCommand] private async Task PickLanguageKeyAsync() { var langKeyPick = await WeakReferenceMessenger.Default.Send(new PickLanguageKeyMessage(_translations, PickedLangKey)); if (langKeyPick != null) { PickedLangKey = langKeyPick.SelectedTranslation == null ? "NoSelection" : langKeyPick.SelectedTranslation.Key; } else { PickedLangKey = "FAIL"; } } }PickLanguageKeyResultVM.cs:
public partial class PickLanguageKeyResultVM : ObservableRecipient { public ObservableCollection<TranslationVM> Translations { get; set; } = []; [ObservableProperty] private TranslationVM? _selectedTranslation; public PickLanguageKeyResultVM(List<TranslationKey> translations, string currentKey) { var loadedLanguages = translations.First().Translations; Translations.Clear(); foreach (var entry in translations) { if (entry.Key != Constants.KEY_LANGUAGE_ORDER) { var trans = new TranslationVM { Key = entry.Key }; for (var l = 0; l < entry.Translations.Count; l++) { trans.LangVals.Add(new LanguageValueVM { Language = loadedLanguages[l], Value = entry.Translations[l] }); } Translations.Add(trans); } } SelectedTranslation = Translations.FirstOrDefault(t => t.Key == currentKey.Trim()); if (SelectedTranslation == null) { SelectedTranslation = Translations[2]; // Color } } [RelayCommand] public void PickKey() { WeakReferenceMessenger.Default.Send(new PickLanguageKeyClosedMessage(this)); } [RelayCommand] public void CancelPick() { WeakReferenceMessenger.Default.Send(new PickLanguageKeyClosedMessage(null)); } } public class PickLanguageKeyMessage(List<TranslationKey> translations, string currentKey) : AsyncRequestMessage<PickLanguageKeyResultVM?> { public string CurrentKey { get; } = currentKey; public List<TranslationKey> Translations { get; set; } = translations; } public class PickLanguageKeyClosedMessage(PickLanguageKeyResultVM? userResult) { public PickLanguageKeyResultVM? UserResult { get; } = userResult; }TranslationsGridDlg.axaml:
<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:WhereIsMyDlgDataGrid.ViewModels" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:DataType="vm:PickLanguageKeyResultVM" WindowStartupLocation="CenterOwner" x:Class="WhereIsMyDlgDataGrid.TranslationsGridDlg" Title="TranslationsGridDlg"> <DockPanel LastChildFill="True"> <StackPanel DockPanel.Dock="Bottom"> <TextBlock Text="Pick language key from above list, then click 'Pick Key' or 'Cancel'" Margin="5" /> <StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" Spacing="5"> <Button Margin="5" Content="Pick Key" Command="{Binding PickKeyCommand}" /> <Button Content="Cancel" Command="{Binding CancelPickCommand}" /> </StackPanel> </StackPanel> <DataGrid x:Name="dtgTranslations" ItemsSource="{Binding Translations}" SelectionMode="Single" CanUserResizeColumns="True" AutoGenerateColumns="False" GridLinesVisibility="All" CanUserSortColumns="True" SelectedItem="{Binding SelectedTranslation}" BorderThickness="1" BorderBrush="Gray" FrozenColumnCount="1"> <DataGrid.Styles> <Style Selector="DataGridColumnHeader"> <Setter Property="FontWeight" Value="Black" /> <Setter Property="FontSize" Value="18" /> </Style> </DataGrid.Styles> <DataGrid.Columns> <DataGridTextColumn Header="Key" Binding="{Binding Key}" /> </DataGrid.Columns> <!--Columns added for each language in file--> </DataGrid> </DockPanel> </Window>TranslationsGridDlg.axaml.cs:
public partial class TranslationsGridDlg : Window { public TranslationsGridDlg() { InitializeComponent(); WeakReferenceMessenger.Default.Register<TranslationsGridDlg, PickLanguageKeyClosedMessage>( this, static (w, m) => w.Close(m.UserResult)); } public void InitLanguageColumns(TranslationVM topEntry) { for (var li = 0; li < topEntry.LangVals.Count; li++) { var column = new DataGridTextColumn { Header = topEntry.LangVals[li].Language, Binding = new Binding($"LangVals[{li}].Value") }; dtgTranslations.Columns.Add(column); } } }TranslationKey.cs:
[DataContract] public class TranslationKey { [DataMember] public string Key { get; set; } = Constants.KEY_LANGUAGE_ORDER; [DataMember] [JsonProperty("Xl8ns")] // Save some JSON file space public List<string> Translations { get; set; } = []; }TranslationVM.cs:
[DataContract] public class TranslationVM { [DataMember] public string Key { get; set; } = string.Empty; [DataMember] public List<LanguageValueVM> LangVals { get; set; } = []; public List<string> GetLanguagesOrder() { var allLanguages = new List<string>(); foreach (var langVal in LangVals) { allLanguages.Add(langVal.Language); } return allLanguages; } public List<string> GetValuesList() { var allTranslations = new List<string>(); foreach (var langVal in LangVals) { allTranslations.Add(langVal.Value); } return allTranslations; } } [DataContract] public class LanguageValueVM : ViewModelBase { [DataMember] public string Language { get; set; } [DataMember] public string Value { get; set; } }Constants.cs:
public class Constants { public const string KEY_LANGUAGE_ORDER = "LangOrder"; public const string KEY_LANGUAGE_NAMES = "LangNames"; public const string LANGUAGE_ENGLISH = "en-US"; public const string LANGUAGE_FRENCH = "fr-FR"; public const string LANGUAGE_SPANISH = "es-ES"; public const string LANGUAGE_GERMAN = "de-DE"; public const string LANGUAGE_ITALIAN = "it-IT"; }What am I doing wrong? Thanks in advance.

