[英]Adding controls to form at runtime with user defined XAML
我想允許我的用戶通過導入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>
這是與道德邏輯答案類似的答案,但我不喜歡他在自己的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}}"/>
首先,我們創建一個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讓我們使用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>
類似地,您可以將此附加屬性綁定到任何面板,如Stack,Dock,Wrap,Grid
<StackPanel local:MyFrameworkObject.RuntimeFrameWorkElement="{Binding RuntimeFrameWorkElement}">
</StackPanel>
或者也可以綁定到ContentControl或ItemsControl(尚未完成)
輸出我使用了與您相同的xaml
注意:如果您將此附加屬性指定給兩個或多個面板或控件,則它將僅添加到第一個。 因為那樣,將設置“已加載xaml”控件的“父級”,因此將無法添加另一個控件。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.