I have a MarkupExtension called XBinding that enhances the concept of RelativeSource by implementing an XPath-like syntax to find the Source of the Binding. It works well when used on a control but fails when used on a DataTrigger. That's because the TargetObject provided in the ProvideValue method is the DataTrigger which is not part of the visual tree so I can't support relative XPaths.
I need to find the control that has the style that contains the data trigger. But you can't get from the data trigger to the host control. You can only get from the data trigger to the root control (window, user control, etc.) and you have to use reflection to do even that.
I have to wait until the root control has initialized and then search its children for one that uses a DataTrigger that is my DataTrigger. Once I have the target control I can find the source control and modify the binding on my DataTrigger. Except I can't because it is sealed at that point. So the trick is to create a new style based on the target controls current style, build a new data trigger based on the old data trigger, remove the old data trigger, add the new data trigger, and finally replace the control's style with the new style.
- Can you give me an example?
- Yes, I think I should.
Start a new Visual Studio project using C# and .Net Core. Call it SealedTrigger.
Change MainWindow.xaml to look like this. You don't need to change the code-behind.
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SealedTrigger"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Orientation="Vertical">
<TextBox Width="100" HorizontalAlignment="Left"/>
<TextBlock Text="Red when TEST">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Black"/>
<Style.Triggers>
<DataTrigger Binding="{local:MyMarkup Path=Text, Source=PreviousTextBox}" Value="TEST">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Window>
The interesting part is the MyMarkup markup extension. In this highly contrived example it will find the previous text box and create a binding to its Text property.
Add a class called MyMarkup and change it to look like this.internal class MyMarkup : MarkupExtension
{
public String? Source { get; set; }
IProvideValueTarget? pvt = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
// We only need to do this if the target object is a data trigger.
// Normally target object is the control itself and rootObject is its ultimate parent. This example does not cover that condition
// Either way we need to provide an event handler for the root object's loaded event.
PropertyInfo? PI = pvt?.GetType().GetProperty("System.Xaml.IRootObjectProvider.RootObject", BindingFlags.Instance | BindingFlags.NonPublic);
return new Binding();
if (Source == "PreviousTextBox")
ContentControl? rootObject = sender as ContentControl;
// previousTextBox will be the last text box before the control that contains this Markup Extension
if (fe is TextBox) previousTextBox = fe as TextBox;
foreach (DataTrigger dt in style.Triggers.OfType<DataTrigger>().Where(dt => dt.Equals(dep)))
// style contains the data trigger that uses this markup extension and fe contains that style.
// Create a new style based on that style and apply it to fe
newStyle.Triggers.Add(newdt);
fe.Style = newStyle;
}
}
}
}
}
PropertyInfo? PI;
PI = depObj.GetType().GetProperty("Content");
if (child != null)
result.Add(child);
if (IsRecursive)
}
}
}
return result;
}
This code creates a new binding to the previous text box's Text property. It builds a new DataTrigger using that binding, Attaches the DataTrigger to a new Style and finally replaces the style of the target control (the textblock).
If you enter TEST into the text box, the text of the TextBlock will turn red.
Now I need to enhance the XBinding markup extension to support this feature.
No comments:
Post a Comment