【问题标题】:Adding controls to form at runtime with user defined XAML使用用户定义的 XAML 在运行时向窗体添加控件
【发布时间】:2014-09-02 15:54:46
【问题描述】:

我想让我的用户通过导入 XAML 在我的程序中定义他们的控件。

作为一个简单的例子,假设用户想要添加一个网格,他们可以在下面导入 XAML。我如何将其添加到论坛中。

<Grid 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Grid.Row="3" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="1,2,1,2">
    <Grid.RowDefinitions>
        <RowDefinition Height="60" />
        <RowDefinition Height="60" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="45" />
        <ColumnDefinition Width="45" />
        <ColumnDefinition Width="45" />
        <ColumnDefinition Width="45" />
        <ColumnDefinition Width="45" />
    </Grid.ColumnDefinitions>

    <Button Grid.Row="0" Grid.Column="0" Content="1" FontSize="14" Margin="1,2,1,2" FontWeight="Bold" />    
</Grid>

【问题讨论】:

标签: c# wpf xaml


【解决方案1】:

这是与道德逻辑答案类似的答案,但我不喜欢他在 ViewModel 中引用 FrameworkElement。这会将您的 ViewModel 耦合到 WPF。相反,我会将用户的内容加载到 ViewModel 中的字符串属性中。

视图模型

public string DynamicXaml
{
    get { return _dynamicXaml; }
    set
    {
       if (_dynamicXaml != value)
       {
           _dynamicXaml = value;
           RaisePropertyChanged(() => DynamicXaml);
       }
    }
}

然后创建一个转换器将字符串转换为 FrameworkElement。

转换器

[ValueConversion(typeof(string), typeof(FrameworkElement))]
public class XamlStringConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        FrameworkElement result = null;
        string xaml = value as string;
        if (!string.IsNullOrEmpty(xaml))
        {
            try
            {
                result = XamlReader.Parse(xaml) as FrameworkElement;
            }
            catch (Exception ex)
            {
                //add logging logic here.
            }
        }
        return result;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }
}

最后,您可以使用 ContentControl 或 ContentPresenter 来显示自定义 xaml。

XAML

<ContentControl x:Name="DynamicControl"
                Content="{Binding Path=DynamicXaml, Converter={StaticResource XamlConverter}}"/>

【讨论】:

    【解决方案2】:

    首先让我们创建一个 AttachedProperty,以便可以将加载的 xaml 添加到我们想要的任何 Panel 或 ContentControl。

    public class MyFrameworkObject : DependencyObject
    {
        public static readonly DependencyProperty RuntimeFrameWorkElementProperty = DependencyProperty.RegisterAttached("RuntimeFrameWorkElement", typeof(FrameworkElement), typeof(MyFrameworkObject),new PropertyMetadata(new PropertyChangedCallback(OnPropertyChanged)));
    
    
        static void  OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SetRuntimeFrameWorkElement(d as UIElement, e.NewValue as FrameworkElement);
        }
        public static void SetRuntimeFrameWorkElement(UIElement element, FrameworkElement value)
        {
            if (element!=null && value != null && value.Parent == null) //The loaded Control can be added to only one Control because its Parent will be set
            {
                var panel = element as Panel;
                if (panel != null)
                {
                    panel.Children.Add(value);
                    return;
                }
                var contentControl = element as ContentControl;
                if (contentControl != null)
                    contentControl.Content = value;
    
                //TODO:ItemsControl
            }
        }
    }
    

    ViewModel :在 ViewModel 中允许创建和加载可以绑定到上述附加属性的属性。

        public class ViewModel : INotifyPropertyChanged
    {
        public ViewModel()
        {
            LoadXaml();
        }
    
        FrameworkElement frameWorkElement;
    
        public FrameworkElement RuntimeFrameWorkElement
        {
            get { return frameWorkElement; }
            set { frameWorkElement = value; OnPropertyChanged("RuntimeFrameWorkElement"); }
        }
    
        public void LoadXaml()
        {
            FileInfo f = new FileInfo(@"F:\myxaml.txt"); //Load xaml from some external file
            if (f.Exists)
                using (var stream = f.OpenRead())
                {
                    this.RuntimeFrameWorkElement = XamlReader.Load(stream) as FrameworkElement;
                }
        }
    
        void OnPropertyChanged(string propName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
    

    xaml.cs

        public MainWindow()
        {
            InitializeComponent();
            DataContext = new ViewModel();
        }
    

    xaml 让我们使用附加属性

    <Window x:Class="StackoverflowQues.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:StackoverflowQues"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <Button  Content="Ok"/>
        <Grid  local:MyFrameworkObject.RuntimeFrameWorkElement="{Binding RuntimeFrameWorkElement}"></Grid>
    </StackPanel>
    

    同样,您可以将此附加属性绑定到任何面板,例如 Stack、Dock、Wrap、Grid

    <StackPanel local:MyFrameworkObject.RuntimeFrameWorkElement="{Binding RuntimeFrameWorkElement}">
    </StackPanel>
    

    或者也可以绑定到 ContentControl 或 ItemsControl(尚未做)

    输出我使用的 xaml 和你的一样

    注意:如果您将此附加属性指定给两个或多个面板或控件,它将仅添加到第一个。因为这样加载的 xaml 控件的父级将被设置并且将无法添加另一个控件。

    【讨论】:

    • -1。这违背了 WPF 中所有已知的、接受的、推荐的、既定的良好实践。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-05
    • 2013-12-20
    • 2014-12-15
    • 2023-03-10
    • 1970-01-01
    • 2018-12-16
    • 1970-01-01
    相关资源
    最近更新 更多