[英]How can I modify xaml resource at run-time?
我在 xaml 中有许多非常相似的资源(略有不同:bingings 中的属性名称、header 中的 static 文本等),它们非常大而复杂:
<Window.Resource>
<A x:Key="a1"> ... </A>
<A x:Key="a2"> ... </A>
...
<B x:Key="b1"> ... />
<B x:Key="b2"> ... />
...
<C x:Key="c1"> ... />
...
</Window.Resource>
我的目标是做到这一点:
<A x:Key="a" ... />
<B x:Key="b" ... />
<C x:Key="c" ... />
...
资源成为一种模板。 但是我需要在使用它之前以某种方式定义一个参数来改变每个这样的资源(例如修改绑定中的属性名称)。
我目前的想法是将资源作为文本进行操作:
var xaml = @"... Text = ""{Binding %PROPERTY%}"" ...";
xaml = xaml.Replace("%PROPERTY%", realPropertyName);
view.Content = XamlReader.Parse(xaml)
但是在代码隐藏中定义 xaml 字符串听起来并不好,它们应该是 xaml 的一部分,在那里使用它们。
于是我有了一个
绝妙的
主意:
// get some resource and restore xaml string for it, yay!
var xaml = XamlWriter.Save(FindResource("some resource"));
但不幸的是XamlWriter非常有限,它没有工作,这样恢复的xaml完全无法使用。
然后我想到将资源定义为字符串:
<clr:String x:Key="a">...</clr:String>
但是 xaml 中的多行字符串和特殊字符使这种方法看起来非常难看。 不要在家里尝试。
理想情况下,我想像以前一样定义资源(拥有智能和东西)并且只想在运行时以某种方式修改它们,因此我的问题。
问题的本地化案例(完全相同)是在DataTemplate
中有参数。 我之前问过关于动态列的问题,这就是为什么我目前定义了这么多类似的资源并试图再次找到解决方案。
我忘了添加资源的具体示例以及某种形式的 MCVE:
<Window.Resources>
<GridViewColumn x:Key="column1">
<GridViewColumn.Header>
<DataTemplate>
<TextBlock Text="Header1" />
</DataTemplate>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value1}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
...
... more similar columns
...
</Window.Resources>
<ListView x:Name="listView" ItemsSource="{Binding Items}">
<ListView.View>
<GridView />
</ListView.View>
</ListView>
添加了一些列
var view = (GridView)listView.View;
foreach(var column in Columns.Where(o => o.Show))
view.Columns.Add((GridViewColumn)FindResouce(column.Key));
其中Columns
集合定义了可以显示哪些列,隐藏哪些列,它们的宽度等。
public class Column
{
public string Key { get; set; } // e.g. "column1"
public bool Show { get; set; }
...
}
要拥有 100 列,我必须定义 100 个"columnX"
资源,但它们非常相似。 我的挑战是只定义一个,然后以某种方式更改动态部分(在这种情况下将"Header1"
更改为"Header2"
和"Value1"
更改为"Value2"
)。
我找到了一种编写 xaml 的方法:
对于此 xaml(资源)需要放入单独的ResourceDictionary
其Build
属性设置为Embedded Resource
内容将如下所示:
<ResourceDictionary ...>
<GridViewColumn x:Key="test" Header="%HEADER%"> <!-- placeholder for header -->
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding %CELL%}" /> <!-- placeholder for cell property name -->
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</ResourceDictionary>
以及加载和修改的代码
// get resource stream
var element = XElement.Load(Assembly.GetExecutingAssembly().GetManifestResourceStream("..."));
// get xaml as text for a specified x:Key
var xaml = element.Descendants().First(o => o.Attributes().Any(attribute => attribute.Name.LocalName == "Key")).ToString();
// dynamic part
xaml = xaml.Replace("%HEADER%", "Some header");
xaml = xaml.Replace("%CELL%", "SomePropertyName");
// xaml to object
var context = new ParserContext();
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
var column = (GridViewColumn)XamlReader.Parse(xaml, context);
view.Columns.Add(column);
我个人根本不使用设计器(仅用于快速导航)并用手编写所有 xaml。 没有设计师的支持对我来说不是问题。
Intellisense 支持和在编译时查看错误非常方便。 忽略构建操作 xaml 将得到充分验证,这是一件好事(与将 xaml 保存在代码后面或文本文件中的string
中相比)。
将资源与窗口/用户控件分离有时会出现问题,例如,如果存在与ElementName
的绑定或对其他资源的引用(未移动到资源字典)等。我目前遇到 BindingProxy 问题,因此此解决方案不是最终解决方案.
更多关注您的 GridView 示例而不是问题标题。
您可以创建一个用户控件或自定义控件来定义单元格内容的外观。
在自定义控件中,您可以定义整个共享样式并为每个单元格应该不同的事物定义依赖属性。
例如,这是一个自定义控件MyCellContent
,它允许绑定Text
属性或绑定MyTextPropertyName
属性,该属性将自动在Text
属性上创建绑定,重定向到MyTextPropertyName
指定的任何内容:
public class MyCellContent : Control
{
static MyCellContent()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCellContent), new FrameworkPropertyMetadata(typeof(MyCellContent)));
}
// Specify a property name that should be used as binding path for Text
public string MyTextPropertyName
{
get { return (string)GetValue(MyTextPropertyNameProperty); }
set { SetValue(MyTextPropertyNameProperty, value); }
}
public static readonly DependencyProperty MyTextPropertyNameProperty =
DependencyProperty.Register("MyTextPropertyName", typeof(string), typeof(MyCellContent), new PropertyMetadata(null, OnTextPropertyChanged));
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BindingOperations.SetBinding(d, TextProperty, new Binding(e.NewValue as string));
}
// The text to be displayed
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyCellContent), new PropertyMetadata(null));
}
在 Themes/Generic.xaml
<Style TargetType="{x:Type local:MyCellContent}">
<!-- Demonstrate the power of custom styling -->
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyCellContent}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock Text="{TemplateBinding Text}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
现在,这是可以建立的。
例如,您可以创建一个CellTemplateSelector
来创建一个单元格模板,其中Text
属性绑定由数据项的另一个属性值确定:
// Specialized template selector for MyGenericData items
public class GridViewColumnCellTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var data = item as MyGenericData;
return CreateCellTemplate(data.MyTargetPropertyName);
}
/// <summary>
/// Create a template with specified binding path
/// </summary>
private DataTemplate CreateCellTemplate(string targetPropertyName)
{
FrameworkElementFactory myCellContentFactory = new FrameworkElementFactory(typeof(MyCellContent));
myCellContentFactory.SetValue(MyCellContent.MyTextPropertyNameProperty, targetPropertyName);
return new DataTemplate
{
VisualTree = myCellContentFactory
};
}
}
使用MyCellContent
控件的另一种不同方法是自定义MyGridviewColumn
,它与上面的模板选择器基本相同,但它允许指定要在Text
属性上使用的绑定,而不是数据驱动的属性选择:
/// <summary>
/// Pre-Templated version of the GridViewColumn
/// </summary>
public class MyGridviewColumn : GridViewColumn
{
private BindingBase _textBinding;
public BindingBase TextBinding
{
get { return _textBinding; }
set
{
if (_textBinding != value)
{
_textBinding = value;
CellTemplate = CreateCellTemplate(value);
}
}
}
/// <summary>
/// Create a template with specified binding
/// </summary>
private DataTemplate CreateCellTemplate(BindingBase contentBinding)
{
FrameworkElementFactory myCellContentFactory = new FrameworkElementFactory(typeof(MyCellContent));
myCellContentFactory.SetBinding(MyCellContent.TextProperty, contentBinding);
return new DataTemplate
{
VisualTree = myCellContentFactory
};
}
}
一些测试数据的使用示例:
<Window.Resources>
<x:Array x:Key="testItems" Type="{x:Type local:MyGenericData}">
<local:MyGenericData Property1="Value 1" Property2="Value 3" MyTargetPropertyName="Property1"/>
<local:MyGenericData Property1="Value 2" Property2="Value 4" MyTargetPropertyName="Property2"/>
</x:Array>
<local:GridViewColumnCellTemplateSelector x:Key="cellTemplateSelector"/>
</Window.Resources>
...
<ListView ItemsSource="{Binding Source={StaticResource testItems}}">
<ListView.View>
<GridView>
<GridViewColumn CellTemplateSelector="{StaticResource cellTemplateSelector}" Header="ABC" Width="100" />
<local:MyGridviewColumn TextBinding="{Binding Property2}" Header="DEF" Width="100" />
</GridView>
</ListView.View>
</ListView>
结果:
gridview 的第一列显示值“值 1”和“值 4”,因为它从第一行的“Property1”和第二行的“Property2”中选择值。 所以显示的数据是由两个数据维度驱动的:指定属性名和目标属性值。
第二列显示值“Value 3”和“Value 4”,因为它使用了指定的绑定表达式“{Binding Property2}”。 因此,显示的数据由指定的绑定表达式驱动,该表达式可以引用数据属性或在数据网格单元中合法绑定的任何其他内容。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.