Динамические элементы управления будут добавлены как дочерние элементы StackPanel в пользовательский элемент управления:
Код: Выделить всё
Код: Выделить всё
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.Generic;
using System.Dynamic;
namespace DynamicBindingControls.ViewModels;
public partial class DynamicSettingsVM : ViewModelBase { // ViewModelBase inherits from ObservableObject
[ObservableProperty]
private string _settingsGroupName;
[ObservableProperty]
private string _settingsGroupText;
[ObservableProperty]
private IBrush? _settingsGroupColor;
[ObservableProperty]
private bool _isBetterResolution;
public dynamic DynamicSettingsProperties { get; set; } = new ExpandoObject();
public DynamicSettingsVM(string defectName, string defectColor) {
SettingsGroupName = defectName;
SettingsGroupColor = new SolidColorBrush(Color.Parse(defectColor));
SettingsGroupText = "settings";
}
public void AddProperty(string propName, object initValue) {
var props = DynamicSettingsProperties as IDictionary;
props.Add(propName, initValue);
}
[RelayCommand]
public void ToggleResolution() {
if (DynamicSettingsProperties.Resolution == "1080p") {
DynamicSettingsProperties.Resolution = "4K";
IsBetterResolution = true;
} else {
DynamicSettingsProperties.Resolution = "1080p";
IsBetterResolution = false;
}
}
}
Код: Выделить всё
Код: Выделить всё
using Avalonia.Controls;
using DynamicBindingControls.ViewModels;
namespace DynamicBindingControls.Views;
public partial class DisplaysDynamicControls : UserControl {
public DisplaysDynamicControls() {
InitializeComponent();
}
private void btnCreateDynamicControls_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) {
var dynaCtl = new DynamicSettings();
dynaCtl.DataContext = new DynamicSettingsVM("TV", "Orange");
dynaCtl.CreateTVSettings();
stpDynControls.Children.Add(dynaCtl);
}
private void btnCreateDynamicControlsRawDC_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) {
var dynaCtl = new DynamicSettings();
dynaCtl.CreateTVSettingsWithDynamicDC();
stpDynControls.Children.Add(dynaCtl);
}
}
Код: Выделить всё
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Media;
using DynamicBindingControls.ViewModels;
using System.Dynamic;
namespace DynamicBindingControls.Views;
public partial class DynamicSettings : UserControl {
public DynamicSettings() {
InitializeComponent();
}
public void CreateTVSettings() {
// Real app loads a lot of this info from a file
var myDC = DataContext as DynamicSettingsVM;
var brightnessSettingLabel = new TextBlock {
Text = "Brightness:",
Margin = new Thickness(5, 5)
};
var brightnessSettingField = new TextBox {
[!TextBox.TextProperty] = new Binding("DynamicSettingsProperties.Brightness")
};
var contrastSettingLabel = new TextBlock {
Text = "Contrast:",
Margin = new Thickness(5, 5)
};
var contrastSettingField = new TextBox {
[!TextBox.TextProperty] = new Binding("DynamicSettingsProperties.Contrast")
};
var channelSettingLabel = new TextBlock {
Text = "Channel:",
Margin = new Thickness(5, 5)
};
var channelSettingField = new TextBox {
[!TextBox.TextProperty] = new Binding("DynamicSettingsProperties.Channel")
};
var resolutionSettingLabel = new TextBlock {
Text = "Resolution:",
Margin = new Thickness(5, 5)
};
var resolutionSettingField = new TextBlock {
[!TextBlock.TextProperty] = new Binding("DynamicSettingsProperties.Resolution")
};
var sleepTimerOnBox = new CheckBox {
Content = "Sleep Timer On:",
[!CheckBox.IsCheckedProperty] = new Binding("DynamicSettingsProperties.IsSleepTimerOn")
};
var betterResolutionLabel = new TextBlock {
Text = "(better)",
[!TextBlock.IsVisibleProperty] = new Binding("IsBetterResolution", BindingMode.TwoWay),
Margin = new Thickness(5, 5)
};
var resToggler = new Button {
Content="Toggle Resolution",
[!Button.CommandProperty] = new Binding("ToggleResolutionCommand")
};
myDC.AddProperty("Brightness", 78);
myDC.AddProperty("Contrast", 54);
myDC.AddProperty("Channel", 24);
myDC.AddProperty("Resolution", "1080p");
myDC.AddProperty("IsSleepTimerOn", true);
stpSettingsGroups.Children.Add(new StackPanel {
Orientation = Avalonia.Layout.Orientation.Horizontal,
Children = { brightnessSettingLabel, brightnessSettingField }
});
stpSettingsGroups.Children.Add(new StackPanel {
Orientation = Avalonia.Layout.Orientation.Horizontal,
Children = { contrastSettingLabel, contrastSettingField }
});
stpSettingsGroups.Children.Add(new StackPanel {
Orientation = Avalonia.Layout.Orientation.Horizontal,
Children = { channelSettingLabel, channelSettingField }
});
stpSettingsGroups.Children.Add(new StackPanel {
Orientation = Avalonia.Layout.Orientation.Horizontal,
Children = { resolutionSettingLabel, resolutionSettingField, betterResolutionLabel }
});
stpSettingsGroups.Children.Add(sleepTimerOnBox);
stpSettingsGroups.Children.Add(resToggler);
}
public void CreateTVSettingsWithDynamicDC() {
dynamic myDC = new ExpandoObject();
myDC.SettingsGroupName = "Television";
myDC.SettingsGroupColor = new SolidColorBrush(Colors.Magenta);
myDC.SettingsGroupText = "settings";
myDC.Brightness = 78;
myDC.Contrast = 54;
myDC.Channel = 24;
myDC.Resolution = "1080p";
myDC.IsBetterResolution = false;
myDC.IsSleepTimerOn = true;
var brightnessSettingLabel = new TextBlock {
Text = "Brightness:",
Margin = new Thickness(5, 5)
};
var brightnessSettingField = new TextBox {
[!TextBox.TextProperty] = new Binding("Brightness") // No "DynamicSettingsProperties."
};
var contrastSettingLabel = new TextBlock {
Text = "Contrast:",
Margin = new Thickness(5, 5)
};
var contrastSettingField = new TextBox {
[!TextBox.TextProperty] = new Binding("Contrast")
};
var channelSettingLabel = new TextBlock {
Text = "Channel:",
Margin = new Thickness(5, 5)
};
var channelSettingField = new TextBox {
[!TextBox.TextProperty] = new Binding("Channel")
};
var resolutionSettingLabel = new TextBlock {
Text = "Resolution:",
Margin = new Thickness(5, 5)
};
var resolutionSettingField = new TextBlock {
[!TextBlock.TextProperty] = new Binding("Resolution")
};
var sleepTimerOnBox = new CheckBox {
Content = "Sleep Timer On:",
[!CheckBox.IsCheckedProperty] = new Binding("IsSleepTimerOn")
};
var betterResolutionLabel = new TextBlock {
Text = "(better)",
[!TextBlock.IsVisibleProperty] = new Binding("IsBetterResolution", BindingMode.TwoWay),
Margin = new Thickness(5, 5)
};
DataContext = myDC;
stpSettingsGroups.Children.Add(new StackPanel {
Orientation = Avalonia.Layout.Orientation.Horizontal,
Children = { brightnessSettingLabel, brightnessSettingField }
});
stpSettingsGroups.Children.Add(new StackPanel {
Orientation = Avalonia.Layout.Orientation.Horizontal,
Children = { contrastSettingLabel, contrastSettingField }
});
stpSettingsGroups.Children.Add(new StackPanel {
Orientation = Avalonia.Layout.Orientation.Horizontal,
Children = { channelSettingLabel, channelSettingField }
});
stpSettingsGroups.Children.Add(new StackPanel {
Orientation = Avalonia.Layout.Orientation.Horizontal,
Children = { resolutionSettingLabel, resolutionSettingField, betterResolutionLabel }
});
stpSettingsGroups.Children.Add(sleepTimerOnBox);
}
}

Нажатие второй кнопки пытается привязать только ExpandoObject в качестве DataContext, к которому также добавлены некоторые свойства, которые явно ожидает XAML:

Что я делаю неправильно и как заставить это работать? Мне не хватает какого-то механизма уведомлений? Спасибо…
EDIT 10 февраля 2026 г., 12:20 CST: я забыл упомянуть, что когда DynamicSettings.CreateTVSettings доходит до операторов stpSettingsGroups.Children.Add, в выводе отладки для каждого динамического свойства есть такая строка:
Код: Выделить всё
[Binding]An error occurred binding 'Text' to 'DynamicSettingsProperties.Brightness' at 'Brightness': 'Could not find a matching property accessor for 'Brightness' on 'System.Dynamic.ExpandoObject'.' (TextBox #59802499)
Код: Выделить всё
public DynamicSettingsVM(string defectName, string defectColor) {
SettingsGroupName = defectName;
SettingsGroupColor = new SolidColorBrush(Color.Parse(defectColor));
SettingsGroupText = "settings";
((INotifyPropertyChanged)DynamicSettingsProperties).PropertyChanged +=
new PropertyChangedEventHandler(HandlePropertyChanges);
}
private void HandlePropertyChanges(
object sender, PropertyChangedEventArgs e) {
var dict = DynamicSettingsProperties as IDictionary;
Trace.WriteLine($"{e.PropertyName} has changed to {dict[e.PropertyName]}.");
}
Код: Выделить всё
Resolution has changed to 4K.
Код: Выделить всё
Exception thrown: 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' in Microsoft.CSharp.dll