簡體   English   中英

如何在運行時修改 xaml 資源?

[英]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(資源)需要放入單獨的ResourceDictionaryBuild屬性設置為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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM