我通过外部 yaml 文件进行了复杂的模型设计,并将视图添加为后面的代码。需要这种结构是因为我构建了一种配置管理器,用户可以在其中通过 yaml 文件创建视图布局。用户界面对我来说工作得很好,所以我得到了包含所有信息的正确视图。还会显示表单元素内的默认值和占位符。但我就是无法将更改后的表单元素返回到后面的代码。
首先,我尝试将模型添加为
ObservableObject
,但更改后的事件侦听器不会被触发。然后我想我需要绑定一些值。所以我尝试了自定义绑定,但我就是无法运行它。
那么也许文本框元素有一些 OnChangeText
属性,但实际上没有。
我正在使用一些库,例如 Lepo 的 CommunityToolkit 和 WPF-UI(用于 UI 元素)
<Page x:Class="ProjektName.Views.Pages.Server.ServerTemplatePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:controls="clr-namespace:ArkServerManager.Controls"
xmlns:lang="clr-namespace:ProjektName.Languages"
xmlns:models="clr-namespace:ProjektName.Models"
xmlns:local="clr-namespace:ProjektName.Views.Pages.Server"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:ServerTemplatePage,
IsDesignTimeCreatable=False}"
d:DesignHeight="650"
d:DesignWidth="800"
Title="ServerAdministrationPage"
ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}"
ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}">
<StackPanel>
<ContentPresenter Content="{Binding ViewModel.RootSections}" />
</StackPanel>
</Page>
public partial class ServerTemplatePage : INavigableView<ServerTemplateViewModel>
{
public ServerTemplateViewModel ViewModel { get; }
public ServerTemplatePage(ServerTemplateViewModel viewModel)
{
ViewModel = viewModel;
DataContext = this;
InitializeComponent();
}
}
public partial class ServerTemplateViewModel : ObservableObject, INavigationAware
{
private ServerSettingsConfigModel? _config;
[ObservableProperty] private ObservableCollection<ServerSettingsConfigModel.SettingsSection>? _sections;
[ObservableProperty] private ObservableCollection<ServerSettingsConfigModel.SettingsSection> _areas;
[ObservableProperty] private StackPanel _rootSections = new();
[ObservableProperty] private ServerSettingsModel _serverSettingsModel = new();
[ObservableProperty] private int _test;
[ObservableProperty] private string _testString = string.Empty;
// private ObservableCollection<ServerSettingsModel> _serverSettingsModel;
// public ObservableCollection<ServerSettingsModel> ServerSettingsModel {
// get => _serverSettingsModel;
// set
// {
// ServerSettingsModel = value;
// OnPropertyChanged("Text");
// }
// }
// private int _name;
//
// public int Name
// {
// get => _name;
// set
// {
// _name = value;
// OnPropertyChanged("Name");
// }
// }
public void OnNavigatedTo()
{
_config = ConfigUtils.GetServerNavigationConfig("Administration");
Sections = _config.Items;
CreateView();
// Areas = _config.Items.ToObservableCollection();
}
private void CreateView()
{
if (Sections == null)
{
return;
}
foreach (var section in Sections)
{
RootSections.Children.Add(new TextBlock
{
Text = section.Text,
Margin = new Thickness(0, 10, 0, 10),
FontTypography = FontTypography.BodyStrong
});
foreach (var property in section.Props)
{
RootSections.Children.Add(new CardControl
{
Header = new DefaultCardControlHeader
{
Title = property.Text,
Description = property.Description
},
Content = GetInputElement(property)
});
}
}
}
private Control GetInputElement(ServerSettingsConfigModel.SettingsSection.SectionProperty property)
{
switch (property.Type)
{
case "int":
var numberBox = new NumberBox
{
Value = property.Default?.ConvertValueToInt(),
PlaceholderText = property.Placeholder,
MinWidth = 200
};
// var numberBinding = new Binding("ViewModel.Name")
// {
// Mode = BindingMode.TwoWay
// };
var numberBinding = new Binding("Test")
{
Source = Test
};
numberBox.SetBinding(NumberBox.ValueProperty, numberBinding);
return numberBox;
case "bool":
var toggleSwitch = new ToggleSwitch
{
IsChecked = property.Default?.ConvertValueToBool(),
OnContent = property.OnText,
OffContent = property.OffText
};
return toggleSwitch;
// Default and for textboxes
default:
var textBox = new System.Windows.Controls.TextBox()
{
// Text = property.Default?.ToString() ?? string.Empty,
// PlaceholderText = property.Placeholder,
MinWidth = 200,
};
var textBinding = new Binding("ViewModel.TestString")
{
Source = TestString,
Mode = BindingMode.TwoWay
};
textBox.SetBinding(System.Windows.Controls.TextBox.TextProperty, textBinding);
return textBox;
}
}
public void OnNavigatedFrom()
{
Console.WriteLine();
}
}
public partial class ServerSettingsConfigModel : ObservableObject
{
public string filename = null!;
#region Config properties
[ObservableProperty] private string _name = null!;
[ObservableProperty] private string _icon = null!;
[ObservableProperty] private ObservableCollection<SettingsSection> _items = null!;
public partial class SettingsSection : ObservableObject
{
[ObservableProperty] private string _text = null!;
[ObservableProperty] private ObservableCollection<SectionProperty> _props = null!;
public partial class SectionProperty : ObservableObject
{
[ObservableProperty] private string _id = null!;
[ObservableProperty] private string _text = null!;
[ObservableProperty] private string _onText = null!;
[ObservableProperty] private string _offText = null!;
[ObservableProperty] private string _description = null!;
[ObservableProperty] private object? _default;
[ObservableProperty] private string _type = null!;
[ObservableProperty] private bool _isOptional;
[ObservableProperty] private bool _isPassword;
[ObservableProperty] private string _placeholder = null!;
}
}
#endregion
}
我对你的问题的想法:
我将使用
itemControl template
和 ObservableCollection<TextBoxViewModel>
来注册文本框:
<!-- MainWindow -->
<Window x:Class="YourNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:YourNamespace"
mc:Ignorable="d"
xmlns:controls="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<!-- Other UI elements -->
<Button Content="Add TextBox" Command="{Binding AddTextBoxCommand}" />
<ItemsControl ItemsSource="{Binding TextBoxes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextChanged="TextBox_TextChanged" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Window>
创建动态文本框的视图模型示例已更改:
// MainViewModel
using System.Collections.ObjectModel;
using System.Windows.Input;
using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.Toolkit.Mvvm.Input;
namespace YourNamespace
{
public class MainViewModel : ObservableObject
{
private ObservableCollection<TextBoxViewModel> textBoxes = new ObservableCollection<TextBoxViewModel>();
public ObservableCollection<TextBoxViewModel> TextBoxes
{
get { return textBoxes; }
set { SetProperty(ref textBoxes, value); }
}
public ICommand AddTextBoxCommand { get; }
public MainViewModel()
{
AddTextBoxCommand = new RelayCommand(AddTextBox);
}
private void AddTextBox()
{
TextBoxViewModel newTextBox = new TextBoxViewModel();
TextBoxes.Add(newTextBox);
}
private void TextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
// Handle the TextBox text changed event here
var textBox = (System.Windows.Controls.TextBox)sender;
string newText = textBox.Text;
// Do something with the new text...
}
}
public class TextBoxViewModel : ObservableObject
{
private string text;
public string Text
{
get { return text; }
set { SetProperty(ref text, value); }
}
}
}