简体   繁体   中英

Adding controls to form at runtime with user defined XAML

I want to allow my users to define their controls in my program by importing XAML.

As a simple example let say the user wanted to add a grid they could import the XAML below. How do I get this added to the forum.

<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>

This is a similar answer to ethicallogics answer but I don't like that he references FrameworkElement in his ViewModel. This couples your ViewModel to WPF. Instead, I'd load the content from the user into a string property in your ViewModel.

ViewModel

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

Then create a converter to convert a string to a FrameworkElement.

Converter

[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;
    }
}

And finally you can use a ContentControl or ContentPresenter to display the custom xaml.

XAML

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

First Lets create an AttachedProperty so that the loaded xaml could be added to any Panel or ContentControl we want.

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 :In ViewModel lets Create and load a property that could be binded to above attached property.

    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 lets use the attachedProperty

<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>

Simillarly you can bind this attachedproperty to any Panel like Stack,Dock,Wrap ,Grid

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

Or can also bind to ContentControl or ItemsControl(yet to do)

Output i have used same xaml as yours

在此处输入图片说明

Note: If you specify this attached property to two or more panels or controls it will be added to the first one only . Because then the Parent of the Loaded xaml control will be set and wont be able to add another control.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM