Note: This is a revision of an earlier design that had the limitation of not being usable in a style, negating its effectiveness quite a bit. However, this new version now works with styles, essentially letting you use it anywhere you can use a binding or a dynamic resource and get the expected results, making it immensely more useful.
DynamicResource 轻松使用转换器的方法。作为来源,但为了遵循 s/o 的最佳实践,我将其作为问答对发布。因此,请查看下面我的答案,了解如何做到这一点。希望能帮助到你!
最佳答案
我一直觉得 WPF 中缺少一些功能:使用动态资源作为绑定(bind)源的能力。我从技术上理解为什么会这样——为了检测变化,绑定(bind)源必须是 DependencyObject 上的一个属性。或在支持 INotifyPropertyChanged 的对象上,而动态资源实际上是 Microsoft 内部的 ResourceReferenceExpression这等同于资源的值(即它不是具有要绑定(bind)的属性的对象,更不用说具有更改通知的对象)--但是,它总是困扰着我,因为它可以在运行时更改,它应该能够根据需要通过转换器推送。
好吧,我相信我终于纠正了这个限制......
输入 动态资源绑定(bind) !
注意:我称它为“绑定(bind)”,但从技术上讲,它是 MarkupExtension我已经定义了诸如 Converter 之类的属性, ConverterParameter , ConverterCulture等,但最终会在内部使用绑定(bind)(实际上有几个!)因此,我根据其用法而不是实际类型来命名它。
但为什么?
那么为什么你甚至需要这样做呢?如何根据用户偏好全局缩放字体大小,同时仍然能够利用相对字体大小,这要归功于 MultiplyByConverter ?或者仅根据 double 定义应用范围的边距如何?资源使用 DoubleToThicknessConverter这不仅可以将其转换为厚度,还可以让您根据布局中的需要屏蔽边缘?或者定义一个基数如何ThemeColor在资源中,然后使用转换器使其变亮或变暗,或根据使用情况更改其不透明度,这要归功于 ColorShadingConverter ?
更好的是,将上述内容实现为 MarkupExtension s 并且您的 XAML 也得到了简化!
<!-- Make the font size 85% of what it would normally be here -->
<TextBlock FontSize="{res:FontSize Scale=0.85)" />
<!-- Use the common margin, but suppress the top edge -->
<Border Margin="{res:Margin Mask=1011)" />
DynamicResourceBinding的实现归功于 Freezable 的巧妙技巧数据类型。具体来说...If you add a Freezable to the Resources collection of a FrameworkElement, any dependency properties on that Freezable object which are set as dynamic resources will resolve those resources relative to that FrameworkElement's position in the Visual Tree.
DynamicResource在 DependencyProperty代理Freezable对象,添加 Freezable到目标的资源集合FrameworkElement ,然后在两者之间设置一个绑定(bind),现在是允许的,因为源现在是 DependencyObject (即 Freezable 。)FrameworkElement在 Style 中使用它时,作为 MarkupExtension在定义的地方提供其值,而不是在最终应用其结果的地方。这意味着当您使用 MarkupExtension 时直接在 FrameworkElement ,它的目标是 FrameworkElement正如你所期望的那样。但是,当您使用 MarkupExtension 时在一种风格中,Style object 是 MarkupExtension 的目标,而不是 FrameworkElement它在哪里应用。由于使用了第二个内部绑定(bind),我也设法绕过了这个限制。public class DynamicResourceBindingExtension : MarkupExtension {
public DynamicResourceBindingExtension(){}
public DynamicResourceBindingExtension(object resourceKey)
=> ResourceKey = resourceKey ?? throw new ArgumentNullException(nameof(resourceKey));
public object ResourceKey { get; set; }
public IValueConverter Converter { get; set; }
public object ConverterParameter { get; set; }
public CultureInfo ConverterCulture { get; set; }
public string StringFormat { get; set; }
public object TargetNullValue { get; set; }
private BindingProxy bindingSource;
private BindingTrigger bindingTrigger;
public override object ProvideValue(IServiceProvider serviceProvider) {
// Get the binding source for all targets affected by this MarkupExtension
// whether set directly on an element or object, or when applied via a style
var dynamicResource = new DynamicResourceExtension(ResourceKey);
bindingSource = new BindingProxy(dynamicResource.ProvideValue(null)); // Pass 'null' here
// Set up the binding using the just-created source
// Note, we don't yet set the Converter, ConverterParameter, StringFormat
// or TargetNullValue (More on that below)
var dynamicResourceBinding = new Binding() {
Source = bindingSource,
Path = new PropertyPath(BindingProxy.ValueProperty),
Mode = BindingMode.OneWay
};
// Get the TargetInfo for this markup extension
var targetInfo = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
// Check if this is a DependencyObject. If so, we can set up everything right here.
if(targetInfo.TargetObject is DependencyObject dependencyObject){
// Ok, since we're being applied directly on a DependencyObject, we can
// go ahead and set all those missing properties on the binding now.
dynamicResourceBinding.Converter = Converter;
dynamicResourceBinding.ConverterParameter = ConverterParameter;
dynamicResourceBinding.ConverterCulture = ConverterCulture;
dynamicResourceBinding.StringFormat = StringFormat;
dynamicResourceBinding.TargetNullValue = TargetNullValue;
// If the DependencyObject is a FrameworkElement, then we also add the
// bindingSource to its Resources collection to ensure proper resource lookup
if (dependencyObject is FrameworkElement targetFrameworkElement)
targetFrameworkElement.Resources.Add(bindingSource, bindingSource);
// And now we simply return the same value as if we were a true binding ourselves
return dynamicResourceBinding.ProvideValue(serviceProvider);
}
// Ok, we're not being set directly on a DependencyObject (most likely we're being set via a style)
// so we need to get the ultimate target of the binding.
// We do this by setting up a wrapper MultiBinding, where we add the above binding
// as well as a second binding which we create using a RelativeResource of 'Self' to get the target,
// and finally, since we have no way of getting the BindingExpressions (as there will be one wherever
// the style is applied), we create a third child binding which is a convenience object on which we
// trigger a change notification, thus refreshing the binding.
var findTargetBinding = new Binding(){
RelativeSource = new RelativeSource(RelativeSourceMode.Self)
};
bindingTrigger = new BindingTrigger();
var wrapperBinding = new MultiBinding(){
Bindings = {
dynamicResourceBinding,
findTargetBinding,
bindingTrigger.Binding
},
Converter = new InlineMultiConverter(WrapperConvert)
};
return wrapperBinding.ProvideValue(serviceProvider);
}
// This gets called on every change of the dynamic resource, for every object it's been applied to
// either when applied directly, or via a style
private object WrapperConvert(object[] values, Type targetType, object parameter, CultureInfo culture) {
var dynamicResourceBindingResult = values[0]; // This is the result of the DynamicResourceBinding**
var bindingTargetObject = values[1]; // The ultimate target of the binding
// We can ignore the bogus third value (in 'values[2]') as that's the dummy result
// of the BindingTrigger's value which will always be 'null'
// ** Note: This value has not yet been passed through the converter, nor been coalesced
// against TargetNullValue, or, if applicable, formatted, both of which we have to do here.
if (Converter != null)
// We pass in the TargetType we're handed here as that's the real target. Child bindings
// would've normally been handed 'object' since their target is the MultiBinding.
dynamicResourceBindingResult = Converter.Convert(dynamicResourceBindingResult, targetType, ConverterParameter, ConverterCulture);
// Check the results for null. If so, assign it to TargetNullValue
// Otherwise, check if the target type is a string, and that there's a StringFormat
// if so, format the string.
// Note: You can't simply put those properties on the MultiBinding as it handles things differently
// than a single binding (i.e. StringFormat is always applied, even when null.
if (dynamicResourceBindingResult == null)
dynamicResourceBindingResult = TargetNullValue;
else if (targetType == typeof(string) && StringFormat != null)
dynamicResourceBindingResult = String.Format(StringFormat, dynamicResourceBindingResult);
// If the binding target object is a FrameworkElement, ensure the BindingSource is added
// to its Resources collection so it will be part of the lookup relative to the FrameworkElement
if (bindingTargetObject is FrameworkElement targetFrameworkElement
&& !targetFrameworkElement.Resources.Contains(bindingSource)) {
// Add the resource to the target object's Resources collection
targetFrameworkElement.Resources[bindingSource] = bindingSource;
// Since we just added the source to the visual tree, we have to re-evaluate the value
// relative to where we are. However, since there's no way to get a binding expression,
// to trigger the binding refresh, here's where we use that BindingTrigger created above
// to trigger a change notification, thus having it refresh the binding with the (possibly)
// new value.
// Note: since we're currently in the Convert method from the current operation,
// we must make the change via a 'Post' call or else we will get results returned
// out of order and the UI won't refresh properly.
SynchronizationContext.Current.Post((state) => {
bindingTrigger.Refresh();
}, null);
}
// Return the now-properly-resolved result of the child binding
return dynamicResourceBindingResult;
}
}
Freezable上面提到过,但它对于其他需要跨越可视化树边界的绑定(bind)代理相关模式也很有帮助。在此处或在 Google 上搜索“BindingProxy”以获取有关该其他用法的更多信息。真是太棒了!public class BindingProxy : Freezable {
public BindingProxy(){}
public BindingProxy(object value)
=> Value = value;
protected override Freezable CreateInstanceCore()
=> new BindingProxy();
#region Value Property
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value),
typeof(object),
typeof(BindingProxy),
new FrameworkPropertyMetadata(default));
public object Value {
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
#endregion Value Property
}
MultiBinding刷新,因为我们无法访问终极版 BindingExpression . (从技术上讲,您可以使用任何支持更改通知的类,但我个人喜欢我的设计明确说明它们的用法。)public class BindingTrigger : INotifyPropertyChanged {
public BindingTrigger()
=> Binding = new Binding(){
Source = this,
Path = new PropertyPath(nameof(Value))};
public event PropertyChangedEventHandler PropertyChanged;
public Binding Binding { get; }
public void Refresh()
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
public object Value { get; }
}
public class InlineMultiConverter : IMultiValueConverter {
public delegate object ConvertDelegate (object[] values, Type targetType, object parameter, CultureInfo culture);
public delegate object[] ConvertBackDelegate(object value, Type[] targetTypes, object parameter, CultureInfo culture);
public InlineMultiConverter(ConvertDelegate convert, ConvertBackDelegate convertBack = null){
_convert = convert ?? throw new ArgumentNullException(nameof(convert));
_convertBack = convertBack;
}
private ConvertDelegate _convert { get; }
private ConvertBackDelegate _convertBack { get; }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
=> _convert(values, targetType, parameter, culture);
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
=> (_convertBack != null)
? _convertBack(value, targetTypes, parameter, culture)
: throw new NotImplementedException();
}
<TextBlock Text="{drb:DynamicResourceBinding ResourceKey=MyResourceKey, Converter={cv:MultiplyConverter Factor=4}, StringFormat='Four times the resource is {0}'}" />
<TextBlock Text="{drb:DynamicResourceBinding MyResourceKey, Converter={cv:MultiplyConverter Factor=4}, StringFormat='Four times the resource is {0}'}" />
DynamicResource完全支持转换器、字符串格式、空值处理等!关于c# - 如何绑定(bind)到 DynamicResource 以便可以使用 Converter 或 StringFormat 等? (修订版 4),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33816511/
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t