Binding in a Style Setter in WinUI 3 - binding

Does WinUI 3 support binding in a Style Setter? I've defined a Style for a NavigationView and the third line is:
<Setter Property="CompactPaneLength" Value="{Binding CurrentCompactPaneLength}" />
This produces a Specified cast is not valid. exception at run time. The DataContext for the page containing the NavigationView is the ViewModel for the page. Both NavigationView.CompactPaneLength and CurrentCompactPaneLength are double and public and CurrentCompactPaneLength is an ObservableObject (from CommunityToolkit.Mvvm.ComponentModel).
The source code for the WinUI 3 (SDK 1.1.2) includes various Setters, such as
<Setter Target="PaneContentGrid.Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=CompactPaneLength}" />
Doing the bindings in code works, if that's what's necessary. But shouldn't the XAML work, too?

Apparently, general bindings in Setters are not supported yet in WinUI 3, although it is a much-requested feature. A workaround is to create a helper class with a DependencyProperty in it that calls a change handler whenever the property is changed/set. The change handler can then create the required binding in code. Kudos to clemens who suggested something like this ages ago for UWP. Here is an example helper class:
internal class BindingHelper
{
#region CompactPaneLengthBindingPath
public static readonly DependencyProperty CompactPaneLengthBindingPathProperty = DependencyProperty.RegisterAttached(
"CompactPaneLengthBindingPath", typeof(string), typeof(BindingHelper), new PropertyMetadata(null, BindingPathChanged));
public static string GetCompactPaneLengthBindingPath(DependencyObject obj)
{
return (string)obj.GetValue(CompactPaneLengthBindingPathProperty);
}
public static void SetCompactPaneLengthBindingPath(DependencyObject obj, string value)
{
obj.SetValue(CompactPaneLengthBindingPathProperty, value);
}
#endregion
#region HeightBindingPath
// another DP is defined here (all of them are strings)
#region ForegroundBindingPath
// and a third one, etc.
// ===================== Change Handler: Creates the actual binding
private static void BindingPathChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is string source) // source property (could add DataContext by setting Value="source#datacontext" for example)
{
DependencyProperty target; // which property is the target of the binding?
if (e.Property == CompactPaneLengthBindingPathProperty) target = NavigationView.CompactPaneLengthProperty;
else if (e.Property == HeightBindingPathProperty) target = FrameworkElement.HeightProperty;
else if (e.Property == ForegroundBindingPathProperty) target = Control.ForegroundProperty;
else throw new System.Exception($"BindingHelper: Unknown target ({nameof(e.Property)}"); // don't know this property
obj.ClearValue(target); // clear previous bindings (and value)
BindingOperations.SetBinding(obj, target, // set new binding (and value)
new Binding { Path = new PropertyPath(source), Mode = BindingMode.OneWay });
}
}
Note that all of the DependencyProperties are of type string and that the target type can be any ancestor type of the control you are working with. For example, the HeightBindingPathProperty binding can be used with any FrameworkElement.
Use the helper in a Style just as you would any Setter, like this:
<Style x:Key="MyNavigationView" TargetType="controls:NavigationView" >
<Setter Property="local:BindingHelper.CompactPaneLengthBindingPath" Value="CurrentCompactPaneLength" />
</Style>
I hope this helps.

Related

Serializing/Deserializing an object that is derived from Java.Lang.Object throws exception (using System.Text.Json)

In one of my .net7-android project, I am trying to serialize an object using System.Text.Json. My object is derived from Java.Lang.Object. I am not intrested in serializing/deserializing the base class (Java.Lang.Object).
The exception I am getting is "Serialization and deserialization of 'System.Type' instances are not supported.". Anyone has any ideas how can it be fixed?
One of the public properties declared by Java.Lang.Object must (directly or indirectly) return an object of type System.Type, thereby causing the exception. Since you don't want to serialize any of these properties anyway, you could create a custom JsonTypeInfo modifier that excludes all properties declared by Java.Lang.Object.
First, define the following extension methods:
public static class JsonExtensions
{
public static Action<JsonTypeInfo> IgnorePropertiesDeclaredBy(Type declaringType)
=> (Action<JsonTypeInfo>) (typeInfo =>
{
if (typeInfo.Kind != JsonTypeInfoKind.Object || !declaringType.IsAssignableFrom(typeInfo.Type))
return;
foreach (var property in typeInfo.Properties)
if (property.GetDeclaringType() == declaringType)
property.ShouldSerialize = static (obj, value) => false;
});
public static Action<JsonTypeInfo> IgnorePropertiesDeclaredBy<TDeclaringType>() => IgnorePropertiesDeclaredBy(typeof(TDeclaringType));
public static Type? GetDeclaringType(this JsonPropertyInfo property) => (property.AttributeProvider as MemberInfo)?.DeclaringType;
}
And now you can use JsonExtensions.IgnorePropertiesDeclaredBy<Java.Lang.Object>() to omit all properties declared by Java.Lang.Object when serializing instances of derived types like so:
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { JsonExtensions.IgnorePropertiesDeclaredBy<Java.Lang.Object>() },
},
// Add other options as required
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
};
var json = JsonSerializer.Serialize(myJavaObject, options);
Note that this will only suppress properties declared by some base type. Suppressing properties declared by an interface that the type implements is not implemented.
Demo fiddle using a mockup of Java.Lang.Object here: https://dotnetfiddle.net/8vNQS6.

Call WebView page method from referenced WinRT Component with AllowForWeb class

I have a XAML page with WebView inside (for example MainPage.xaml). Also I have WinRT Component with class marked with [AllowForWeb] attribute. This component is referenced from project where MainPage.xaml located and in code-behind AddWebAllowedObject method is used. And I can't reference main project back because of circular dependency.
How to call MainPage.xaml.cs methods from component class? Very usual situation. Is there are some standard way to do it?
For example. I have a method inside RT component that could be called from JavaScript
public void ShowMessage(string message)
{
// I want to call here function from MainPage.xaml.cs
}
How to call MainPage.xaml.cs methods from component class? Very usual situation. Is there are some standard way to do it?
Yes, you can pass the method from MainPage.xaml.cs to Windows Runtime Component through delegate(Currently it's very limited to use delegate in Runtime Component using C#, see this case, so I use C++ as demo).
For Runtime Component Class MyClass.h:
public delegate Platform::String^ MyFunc(int a, int b);
public ref class MyClass sealed
{
public:
MyClass();
static Platform::String^ MyMethod(MyFunc^ func)
{
Platform::String^ abc=func(4, 5);
return abc;
}
};
And you can use the delegate in code behind like below:
using MyComponentCpp;
private void myBtn_Click(object sender, RoutedEventArgs e)
{
String abc=MyClass.MyMethod(MyMethod);
myTb.Text = abc;
}
private String MyMethod(int a, int b)
{
return (a.ToString() + b.ToString());//replace this line with your own logic.
}
And here is the complete Demo: TestProject.
Thankfully to #Elvis Xia who has gived me idea, I has found a solution how to do it without C++.
I have create a third project as Class Library. It doesn't has restrictions to use Action. This library I have referenced from main project and from WinRT component. Code of class inside library:
public class BridgeClass
{
public static event Action<string> MessageReceived;
public static void Broadcast(string message)
{
if (MessageReceived != null) MessageReceived(message);
}
}
Code inside main project with webview is:
// place somewhere
BridgeClass.MessageReceived += ShowMessage;
// ....... and add a method
void ShowMessage(string msg)
{
}
And now i can call this code from WinRT component:
public void ShowMessage(string message)
{
BridgeClass.Broadcast("lalala");
}

Ninject selecting parameterless constructor when using implicit self-binding

I am using Ninject version 3 in an MVVM-type scenario in a .NET WPF application. In a particular instance I am using a class to act as coordinator between the view and its view model, meaning the coordinator class is created first and the view and view model (along with other needed services) are injected into it.
I have bindings for the services, but I have not created explicit bindings for the view/view model classes, instead relying on Ninject's implicit self-binding since these are concrete types and not interfaces.
A conceptual version of this scenario in a console app is shown below:
class Program
{
static void Main(string[] args)
{
StandardKernel kernel = new StandardKernel();
kernel.Bind<IViewService>().To<ViewService>();
//kernel.Bind<View>().ToSelf();
//kernel.Bind<ViewModel>().ToSelf();
ViewCoordinator viewCoordinator = kernel.Get<ViewCoordinator>();
}
}
public class View
{
}
public class ViewModel
{
}
public interface IViewService
{
}
public class ViewService : IViewService
{
}
public class ViewCoordinator
{
public ViewCoordinator()
{
}
public ViewCoordinator(View view, ViewModel viewModel, IViewService viewService)
{
}
}
If you run this code as-is, the kernel.Get<> call will instantiate the ViewCoordinator class using the parameterless constructor instead of the one with the dependencies. However, if you remove the parameterless constructor, Ninject will successfully instantiate the class with the other constructor. This is surprising since Ninject will typically use the constructor with the most arguments that it can satisfy.
Clearly it can satisfy them all thanks to implicit self-binding. But if it doesn't have an explicit binding for one of the arguments it seems to first look for alternate constructors it can use before checking to see if it can use implicit self-binding. If you uncomment the explicit Bind<>().ToSelf() lines, the ViewController class will instantiate correctly even if the parameterless constructor is present.
I don't really want to have to add explicit self-bindings for all the views and view models that may need this (even though I know that burden can be lessened by using convention-based registration). Is this behavior by design? Is there any way to tell Ninject to check for implicit self-binding before checking for other usable constructors?
UPDATE
Based on cvbarros' answer I was able to get this to work by doing my own implementation of IConstructorScorer. Here's the changes I made to the existing code to get it to work:
using Ninject.Selection.Heuristics;
class Program
{
static void Main(string[] args)
{
StandardKernel kernel = new StandardKernel();
kernel.Components.RemoveAll<IConstructorScorer>();
kernel.Components.Add<IConstructorScorer, MyConstructorScorer>();
kernel.Bind<IViewService>().To<ViewService>();
ViewCoordinator viewCoordinator = kernel.Get<ViewCoordinator>();
}
}
using System.Collections;
using System.Linq;
using Ninject.Activation;
using Ninject.Planning.Targets;
using Ninject.Selection.Heuristics;
public class MyConstructorScorer : StandardConstructorScorer
{
protected override bool BindingExists(IContext context, ITarget target)
{
bool bindingExists = base.BindingExists(context, target);
if (!(bindingExists))
{
Type targetType = this.GetTargetType(target);
bindingExists = (
!targetType.IsInterface
&& !targetType.IsAbstract
&& !targetType.IsValueType
&& targetType != typeof(string)
&& !targetType.ContainsGenericParameters
);
}
return bindingExists;
}
private Type GetTargetType(ITarget target)
{
var targetType = target.Type;
if (targetType.IsArray)
{
targetType = targetType.GetElementType();
}
if (targetType.IsGenericType && targetType.GetInterfaces().Any(type => type == typeof(IEnumerable)))
{
targetType = targetType.GetGenericArguments()[0];
}
return targetType;
}
}
The new scorer just sees if a BindingExists call failed by overriding the BindingExists method and if so it checks to see if the type is implicitly self-bindable. If it is, it returns true which indicates to Ninject that there is a valid binding for that type.
The code making this check is copied from the SelfBindingResolver class in the Ninject source code. The GetTargetType code had to be copied from the StandardConstructorScorer since it's declared there as private instead of protected.
My application is now working correctly and so far I haven't seen any negative side effects from making this change. Although if anyone knows of any problems this could cause I would welcome further input.
By default, Ninject will use the constructor with most bindings available if and only if those bindings are defined (in your case they are implicit). Self-bindable types do not weight when selecting which constructor to use.
You can mark which constructor you want to use by applying the [Inject] attribute to it, this will ensure that constructor is selected.
If you don't want that, you can examine StandardConstructorScorer to see if that will fit your needs. If not, you can replace the IConstructorScorer component of the Kernel with your own implementation.

Doing interception with structuremap

I'm trying to do some attribute-based interception using structuremap but I'm struggling to tie up the last loose ends.
I have a custom Registry that scans my assemblies and in this Registry I have defined the following ITypeInterceptor whose purpose it is to match types decorated with the given attribute and then apply the interceptor if matched. The class is defined as such:
public class AttributeMatchTypeInterceptor<TAttribute, TInterceptor>
: TypeInterceptor
where TAttribute : Attribute
where TInterceptor : IInterceptor
{
private readonly ProxyGenerator m_proxyGeneration = new ProxyGenerator();
public object Process(object target, IContext context)
{
return m_proxyGeneration.CreateInterfaceProxyWithTarget(target, ObjectFactory.GetInstance<TInterceptor>());
}
public bool MatchesType(Type type)
{
return type.GetCustomAttributes(typeof (TAttribute), true).Length > 0;
}
}
//Usage
[Transactional]
public class OrderProcessor : IOrderProcessor{
}
...
public class MyRegistry : Registry{
public MyRegistry()
{
RegisterInterceptor(
new AttributeMatchTypeInterceptor<TransactionalAttribute, TransactionInterceptor>());
...
}
}
I'm using DynamicProxy from the Castle.Core to create the interceptors, but my problem is that the object returned from the CreateInterfaceProxyWithTarget(...) call does not implement the interface that triggered the creation of the target instance in structuremap (i.e IOrderProcessor in example above). I was hoping that the IContext parameter would reveal this interface, but I can only seem to get a hold of the concrete type (i.e. OrderProcessor in example above).
I'm looking for guidance on how to have this scenario work, either by calling the ProxyGenerator to return an instance that implements all interfaces as the target instance, by obtaining the requested interface from structuremap or through some other mechanism.
I actually got something working with a slight caveat so I'll just post this as the answer. The trick was to obtain the interface and pass that into the CreateInterfaceProxyWithTarget. My only problem was that I could not find a way to query the IContext about which interface it was currently resolving so I ended up just looking up the first interface on the target which worked for me. See code below
public class AttributeMatchTypeInterceptor<TAttribute, TInterceptor> :
TypeInterceptor
where TAttribute : Attribute
where TInterceptor : IInterceptor
{
private readonly ProxyGenerator m_proxyGeneration = new ProxyGenerator();
public object Process(object target, IContext context)
{
//NOTE: can't query IContext for actual interface
Type interfaceType = target.GetType().GetInterfaces().First();
return m_proxyGeneration.CreateInterfaceProxyWithTarget(
interfaceType,
target,
ObjectFactory.GetInstance<TInterceptor>());
}
public bool MatchesType(Type type)
{
return type.GetCustomAttributes(typeof (TAttribute), true).Length > 0;
}
}
Hope this helps someone

Ninject binds to the wrong anonymous method when there is more than one method bound type

I am building a framework that I don't want to couple to a particular IOC container so have created a layer on top of Ninject / structuremap etc.
I have a binding class that accepts a Func to allow binding to a method.
For example
public class Binding
{
public Type Source { get; set; }
public Func<object> Method {get; set; }
public Scope { get; set; }
}
If I have a binding like...
var binding = new Binding() {
Source = typeof(IRepository),
Method = () => new Repository(new LinqToSqlRepository(connectionString)),
Scope = Scope.HttpRequest
};
The framework wrapping Ninject creates Ninject bindings for my generic binding like this
Module :NinjectModule
{
IList<Binding> _Bindings;
public Module(IList<Binding> bindings)
{
_Bindings = bindings;
}
public override void Load() {
foreach (var binding in _Bindings) {
switch(binding.Scope) {
case IocScope.HttpRequest:
Bind(binding.Source).ToMethod(c => binding.Method()).InRequestScope();
break;
// ... omitted for brevity
}
}
}
}
This works fine when there is only one binding being bound to a method. When there are multiple bindings being bound within the same module to methods however the incorrect type is returned. From debugging, it looks as if the last binding is always used.
Thus the problem with an example;
var binding1 = new Binding() {
Source = typeof(IRepository),
Method = () => new Repository(new LinqToSqlRepository(connectionString)),
Scope = Scope.HttpRequest
};
var binding2 = new Binding() {
Source = typeof(ICalendar),
Method = () => new MvcCalendar( ..... )
Scope = Scope.HttpRequest
};
At runtime when Ninject is requested to new up an MVC Controller which takes in an IRepository and an ICalendar, I receive a type conversion error saying that a MvcCalendar cannot be converted to an IRepository. I have discovered that for some reason the last binding is always being returned for the first requested type.
This is a highly simplified version of what is really going on to try and highlight the actual issue, the wrong method being bound to a requested type when there are multiple method bindings. I hope this still explains the issue though.
This appears to be related to some sort of closure scoping issue. I also wonder whether Ninject is getting is getting confused by the Func instead of Func usage.
Unit Test Example
Here is a test module I load into my custom IOC container. This does not depend on any particular IOC framework. When I instantiate a NinjectIocContainer to handle the DI, the internal binding of this in Ninject occurs as example further up (see NinjectModule)
public class MultipleMethodBoundTypesModule : IocModule
{
public override void Load()
{
Bind<IPerson>().To(() => new Person()).In(IocScope.Transient);
Bind<IRobot>().To(() => new Robot(new Person())).In(IocScope.Transient);
}
}
Here is a simple test that tries to retrieve each of the types.
[Test]
public void Expect_That_Multiple_Method_Bound_Types_Can_Exist_Within_The_Same_Module()
{
// arrange
var container = Get_Container_With_Module(new MultipleMethodBoundTypesModule());
// act
var person = container.Get<IPerson>();
var robot = container.Get<IRobot>();
// assert
Assert.IsNotNull(person);
Assert.IsNotNull(robot);
}
As explained eariler, this throws a type conversion where the last closure (for the robot) is being bound to a person.
TestCase 'Ioc.Test.NinjectContainerTest.Expect_That_Multiple_Method_Bound_Types_Can_Exist_Within_The_Same_Module'
failed: System.InvalidCastException : Unable to cast object of type 'Ioc.Test.Robot' to type 'Ioc.Test.IPerson'.
at System.Linq.Enumerable.d__b11.MoveNext()
at System.Linq.Enumerable.Single[TSource](IEnumerable1 source)
at Ninject.ResolutionExtensions.Get[T](IResolutionRoot root, IParameter[] parameters)
NinjectIocContainer.cs(40,0): at Ioc.Ninject.NinjectIocContainer.GetTInstance
IocTestBase.cs(149,0): at Ioc.Test.IocTestBase.Expect_That_Multiple_Method_Bound_Types_Can_Exist_Within_The_Same_Module()
In the snippet:
Bind(binding.Source).ToMethod(binding.Method()).InRequestScope();
You're dereferencing the Method bit. You want to be doing that as either binding.Method or ()=>binding.Method() (the former may not unambiguously be inferrable based on the C# type inference rules).
You mentioned this is heavily stripped down from your real code. As a result, this may not be the actual issue. I'd still be betting on some form of closure confusion though (see the section Comparing capture strategies: complexity vs power in this CSID excerpt for a nice walkthrough).
You also probably meant to use .InScope(binding.Scope) rather than .InRequestScope() too,.

Resources