Create a BindableProperty depending on multiple properties - mvvm

I'm trying to bind a UI element to different model properties A, B and AB. The first two properties A and B are controlled by two sliders. The third property AB is the sum of A and B. For each of the three properties there is a label displaying its value.
Now if I move one of the sliders, the corresponding label updates its Text. But the label for the combined property AB is not updated. Probably no "property changed" event is fired, since there is no setter for AB.
Is there any possibility for binding to such an "aggregated" property?
Here is the bindable object containing the properties A, B and AB:
public class Settings: BindableObject
{
public static readonly BindableProperty AProperty = BindableProperty.Create<Settings, double>(p => p.A, 0);
public static readonly BindableProperty BProperty = BindableProperty.Create<Settings, double>(p => p.B, 0);
public static readonly BindableProperty ABProperty = BindableProperty.Create<Settings, double>(p => p.AB, 0);
public double A {
get{ return (double)GetValue(AProperty); }
set{ SetValue(AProperty, (double)value); }
}
public double B {
get{ return (double)GetValue(BProperty); }
set{ SetValue(BProperty, (double)value); }
}
public double AB {
get{ return A + B; }
}
}
And here is the page containing both sliders and the three labels:
public class App : Application
{
public App()
{
var settings = new Settings();
var sliderA = new Slider();
sliderA.ValueChanged += (sender, e) => settings.A = e.NewValue;
var sliderB = new Slider();
sliderB.ValueChanged += (sender, e) => settings.B = e.NewValue;
var labelA = new Label{ BindingContext = settings };
labelA.SetBinding(Label.TextProperty, "A");
var labelB = new Label{ BindingContext = settings };
labelB.SetBinding(Label.TextProperty, "B");
var labelAB = new Label{ BindingContext = settings };
labelAB.SetBinding(Label.TextProperty, "AB");
MainPage = new ContentPage {
Content = new StackLayout {
VerticalOptions = LayoutOptions.Center,
Children = { sliderA, sliderB, labelA, labelB, labelAB },
},
};
}
}
This is what the running application looks like on iOS:
The last label should display the sum of the first two numbers.
Edit:
I wonder why I can't write
public static readonly BindableProperty ABProperty =
BindableProperty.Create<Settings, double>(p => p.A + p.B, 0);
But this yields the run-time error "System.TypeInitializationException: An exception was thrown by the type initializer for AggregatedBindablePropertyMnml.Settings ---> System.Exception: getter must be a MemberExpression"

Based on the suggestion from Taekahn (updating AB within the setters of A and B) I came up with the following solution.
By overriding the OnPropertyChanged method and setting the ABProperty, the bound label text is updated as well. In contrast to modifying each setter individually, this way we only need to modify the Settings class at one place.
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
SetValue(ABProperty, A + B);
}
Now both sliders impact the third label:

Related

Xamarin.Forms: Is it possible to dynamically add rows to a grid without the ViewModel calling into the VIew?

On a page where I am displaying a receipt to a user, I have a section which lists the subtotal, any taxes, and the total. Since the taxes applicable vary by region and based on the products being sold, the number of rows in this section will vary. For example, here are 3 separate orders, one with GST and PST, one with just GST, and one with neither:
I've accomplished this by putting just the SubTotal in the grid in XAML, and adding the rest in the code-behind in a method I call from the ViewModel. However, I'd really like to avoid doing it this way, so I'm wondering if there is an approach to accomplishing this which doesn't require having the ViewModel know about the View.
A ListView is not suitable here for a number of reasons:
These controls are inside of a ScrollView, and having a ListView inside of a ScrollView causes all sorts of weird problems.
I would like to keep the columns as narrow as their widest element. This is possible with a Grid, but a ListView would take up the entire width of its parent no matter what.
I neither need nor want for my rows to be selectable
So is there a way I can do this without the ViewModel knowing about the View and without resorting to using a ListView?
One way to encapsulate the functionality you require so that the view and the view model are not coupled is by creating a user control.
I created a new user control called TotalsGridControl. Here is the XAML
<?xml version="1.0" encoding="UTF-8"?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ScratchPad.UserControls.TotalsGridControl"
x:Name="TotalsGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
And here is the code behind.
public partial class TotalsGridControl : Grid
{
public TotalsGridControl()
{
InitializeComponent();
}
public static readonly BindableProperty TotalsProperty =
BindableProperty.Create(nameof(Totals), typeof(List<TotalItem>), typeof(TotalsGridControl), null,
BindingMode.OneWay, null, OnTotalsChanged);
private static void OnTotalsChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var control = (TotalsGridControl)bindable;
if (control != null)
{
if (newvalue is List<TotalItem> totals)
{
var rowNumber = -1;
double grandTotal = 0;
foreach (var totalItem in totals)
{
grandTotal += totalItem.Value;
var descLabel = new Label {Text = totalItem.Description};
var valueLabel = new Label { Text = totalItem.Value.ToString("c") };
rowNumber++;
control.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto});
control.Children.Add(descLabel, 0, rowNumber);
control.Children.Add(valueLabel, 1, rowNumber);
}
var grandTotalDescLabel = new Label { Text = "Total" };
var grandTotalValueLabel = new Label { Text = grandTotal.ToString("c") };
rowNumber++;
control.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
control.Children.Add(grandTotalDescLabel, 0, rowNumber);
control.Children.Add(grandTotalValueLabel, 1, rowNumber);
}
}
}
public List<TotalItem> Totals
{
get => (List<TotalItem>)GetValue(TotalsProperty);
set => SetValue(TotalsProperty, value);
}
}
I used a bindable property to allow a list of TotalItem to be bound to the user control.
Here is the data in the view model
public List<TotalItem> Totals { get; set; }
Totals = new List<TotalItem>
{
new TotalItem {Description = "SubTotal", Value = 99.91},
new TotalItem {Description = "GST", Value = 5.0},
new TotalItem {Description = "PST", Value = 4.9}
};
and here is the XAML in the page
<userControls:TotalsGridControl Totals="{Binding Totals}"/>
And the output
Before:
public class ReceiptPageModel : PageModelBase
{
private Receipt _receipt;
public Receipt Receipt
{
get => _receipt;
private set => Set(ref _receipt, value);
}
public override void Init(object initData)
{
Receipt = (Receipt) initData;
((ReceiptPage) CurrentPage).AddTaxes(Receipt);
}
}
After:
public class ReceiptPageModel : PageModelBase
{
private Receipt _receipt;
public Receipt Receipt
{
get => _receipt;
private set => Set(ref _receipt, value);
}
public override void Init(object initData)
{
Receipt = (Receipt) initData;
}
}
public partial class ReceiptPage : FreshBaseContentPage
{
public ReceiptPage()
{
InitializeComponent();
BindingContextChanged += HandlePageModelAdded;
}
private void HandlePageModelAdded(object sender, EventArgs e)
{
var pageModel = (ReceiptPageModel)BindingContext;
if (pageModel.Receipt != null)
{
AddTaxes(pageModel.Receipt);
}
else
{
pageModel.PropertyChanged += (s, args) =>
{
if (args.PropertyName == nameof(pageModel.Receipt))
AddTaxes(pageModel.Receipt);
};
}
}
private void AddTaxes(Receipt receipt)
{
...
}
}

set property of new instance

thank you for helping.
First, I created a form with a (user defined) property.
as see below
public partial class nfrmtableitem : Form
{
private DataRow _datarow;
public DataRow U_Table_Row { get { return _datarow; } set { _datarow = value; } }
public nfrmtableitem()
{
InitializeComponent();
}
}
And I create second form with property as type of Form.
as see below
public partial class nftableshow : Form
{
private DataTable _datatable;
public DataTable U_DataTable { get { return _datatable; } set { _datatable = value; } }
private Form _inputform1;
public Form U_DGV_InputForm1 { get { return _inputform1; } set { _inputform1 = value; } }
}
when call it:
any where
nftableshow newfrmtableshow = new nftableshow()
{
Name = "newfrmtableshow",
Text = "Show the table",
MdiParent = this,
U_DGV_InputForm1 = new nfrmtableitem(),
};
newfrmtableshow.Show();
But I can not use the first form property in second form.
and the property is not in instance.
//the button in second form
private void button1_Click_Click(object sender, EventArgs e)
{
Form f1 = _inputform1 as Form;
/*
* {
* U_Table_Row = db.maindataset.Tables["customer"].NewRow(),
* };
*/
f1.Show();
}
Question:
How can I use the First form with specific (user defined) property in second form.
Regards
You should probably use dot notation to access the property of the first form. Try using
//the button in second form
private void button1_Click_Click(object sender, EventArgs e)
{
Form f1 = _inputform1 as Form;
{
f1.U_Table_Row = db.maindataset.Tables["customer"].NewRow(),
};
f1.Show();
}

Autofac PropertiesAutowired - Is it possible to ignore a one or more properties?

Despite the advice to pass dependencies through the constructor I've found that the development cost of having parameterless constructors and then autowiring all of the properties on everything is significantly less and makes the application much easier to develop out and maintain. However sometimes (on a view model for example) I have a property that is registered with the container, but that I don't want to populate at construction (for example the selected item bound to a container).
Is there any way to tell the container to ignore certain properties when it autowires the rest?
At the moment I'm just resetting the properties marked with an attribute in the on activated event a la:
public static IRegistrationBuilder<TLimit, ScanningActivatorData, TRegistrationStyle>
PropertiesAutowiredExtended<TLimit, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, ScanningActivatorData, TRegistrationStyle> builder)
{
builder.ActivatorData.ConfigurationActions.Add(
(type, innerBuilder) =>
{
var parameter = Expression.Parameter(typeof(object));
var cast = Expression.Convert(parameter, type);
var assignments = type.GetProperties()
.Where(candidate => candidate.HasAttribute<NotAutowiredAttribute>())
.Select(property => new { Property = property, Expression = Expression.Property(cast, property) })
.Select(data => Expression.Assign(data.Expression, Expression.Default(data.Property.PropertyType)))
.Cast<Expression>()
.ToArray();
if (assignments.Any())
{
var #action = Expression
.Lambda<Action<object>>(Expression.Block(assignments), parameter)
.Compile();
innerBuilder.OnActivated(e =>
{
e.Context.InjectUnsetProperties(e.Instance);
#action(e.Instance);
});
}
else
{
innerBuilder.OnActivated(e => e.Context.InjectUnsetProperties(e.Instance));
}
});
return builder;
}
Is there a better way to do this?
Not sure that this is a better one, but you can go from another side, register only needed properties via WithProperty syntax. Pros is that Autofac doesn't resolve unnecessary services. Here's a working example:
public class MyClass
{
public MyDependency MyDependency { get; set; }
public MyDependency MyExcludeDependency { get; set; }
}
public class MyDependency {}
public class Program
{
public static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterType<MyDependency>();
builder.RegisterType<MyClass>().WithPropertiesAutowiredExcept("MyExcludeDependency");
using (var container = builder.Build())
{
var myClass = container.Resolve<MyClass>();
Console.WriteLine(myClass.MyDependency == null);
Console.WriteLine(myClass.MyExcludeDependency == null);
}
}
}
public static class PropertiesAutowiredExtensions
{
// Extension that registers only needed properties
// Filters by property name for simplicity
public static IRegistrationBuilder<TLimit, TReflectionActivatorData, TRegistrationStyle>
WithPropertiesAutowiredExcept<TLimit, TReflectionActivatorData, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, TReflectionActivatorData, TRegistrationStyle> registrationBuilder,
params string[] propertiesNames)
where TReflectionActivatorData : ReflectionActivatorData
{
var type = ((IServiceWithType)registrationBuilder.RegistrationData.Services.Single()).ServiceType;
foreach (var property in type
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(pi => pi.CanWrite && !propertiesNames.Contains(pi.Name)))
{
// There's no additional checks like in PropertiesAutowired for simplicity
// You can add them from Autofac.Core.Activators.Reflection.AutowiringPropertyInjector.InjectProperties
var localProperty = property;
registrationBuilder.WithProperty(
new ResolvedParameter(
(pi, c) =>
{
PropertyInfo prop;
return pi.TryGetDeclaringProperty(out prop) &&
prop.Name == localProperty.Name;
},
(pi, c) => c.Resolve(localProperty.PropertyType)));
}
return registrationBuilder;
}
// From Autofac.Util.ReflectionExtensions
public static bool TryGetDeclaringProperty(this ParameterInfo pi, out PropertyInfo prop)
{
var mi = pi.Member as MethodInfo;
if (mi != null && mi.IsSpecialName && mi.Name.StartsWith("set_", StringComparison.Ordinal)
&& mi.DeclaringType != null)
{
prop = mi.DeclaringType.GetProperty(mi.Name.Substring(4));
return true;
}
prop = null;
return false;
}
}

How to assign/opt from multiple delegates for a 'moled' method?

I am currently examining Moles from the outside while I wait for my VS 2010 license, and I wonder whether Moles allows me to:
provide the ability to assígn multiple mole delegates for a method being moled, perhaps at a test fixture setup level?
switch in runtime in my test case, which of my mole delegates must be invoked for the upcoming call(s) to the moled method being isolated?
Any hints?
Best Answer:
It is much easier and makes far more sense to include gating logic in the detour method, than using two stubs for the same method! For example, MyMethod reads data from three different files on disk, each requiring different mock data to be returned. We may detour System.IO.File.OpenRead and gate the return value by analyzing the input parameters of OpenRead:
TEST METHOD:
[TestMethod]
[HostType("Moles")]
public void Test()
{
System.IO.Moles.MFile.OpenReadString = filePath => {
var mockStream = new System.IO.FileStream();
byte[] buffer;
switch (filePath)
{
case #"C:\DataFile.dat":
mockStream.Write(buffer, 0, 0); // Populate stream
break;
case #"C:\TextFile.txt":
mockStream.Write(buffer, 0, 0); // Populate stream
break;
case #"C:\LogFile.log":
mockStream.Write(buffer, 0, 0); // Populate stream
break;
}
return mockStream;
};
var target = new MyClass();
target.MyMethod();
}
TARGET TYPE:
using System.IO;
public class MyClass
{
public void MyMethod()
{
var fileAData = File.OpenRead(#"C:\DataFile.dat");
var fileBData = File.OpenRead(#"C:\TextFile.txt");
var fileCData = File.OpenRead(#"C:\LogFile.log");
}
}
Direct Answer to Your Questions:
Yes to #1: instantiate one type for each detour, and then use each for the desired behavior. And, yes to #2: act upon one instance of the mole type or the other. This requires addition of method input parameters or class constructor injection.
For example, MyMethod reads three data files from disk, and you need to pass back three different data mocks. MyMethod requires three parameters, an overtly intrusive solution. (Note input parameters are FileInfo type; because, System.IO>File is static and can not be instantiated: For example:
TEST METHOD:
[TestMethod]
[HostType("Moles")]
public void Test()
{
var fileInfoMoleA = new System.IO.Moles.MFileInfo();
fileInfoMoleA.OpenRead = () => { return new FileStream(); };
var fileInfoMoleB = new System.IO.Moles.MFileInfo();
fileInfoMoleB.OpenRead = () => { return new FileStream(); };
var fileInfoMoleC = new System.IO.Moles.MFileInfo();
fileInfoMoleC.OpenRead = () => { return new FileStream(); };
var target = new MyClass();
target.MyMethod(fileInfoMoleA, fileInfoMoleB, fileInfoMoleC);
}
TARGET TYPE:
using System.IO;
public class MyClass
{
// Input parameters are FileInfo type; because, System.IO.File
// is a static class, and can not be instantiated.
public void MyMethod(FileInfo fileInfoA, FileInfo fileInfoB, FileInfo fileInfoC)
{
var fileAData = fileInfoA.OpenRead();
var fileBData = fileInfoB.OpenRead();
var fileCData = fileInfoC.OpenRead();
}
}
UPDATE:
In response to #Chai comment, it is possible to create common methods, within the test project, that may be referenced as the mole detour delegate. For example, you may wish to write a common method that may be referenced by any unit test, that sets up a variety of pre-configured scenarios. The following example displays how a parameterized method could be used. Get creative -- they're just method calls!
TARGET TYPES:
namespace PexMoleDemo
{
public class MyClass
{
private MyMath _math;
public MyClass()
{
_math = new MyMath() { left = 1m, right = 2m };
}
public decimal GetResults()
{
return _math.Divide();
}
}
public class MyOtherClass
{
private MyMath _math;
public MyOtherClass()
{
_math = new MyMath() { left = 100m, right = 200m };
}
public decimal Divide()
{
return _math.Divide();
}
}
public class MyMath
{
public decimal left { get; set; }
public decimal right { get; set; }
public decimal Divide()
{
return left / right;
}
}
}
TEST METHODS:
ArrangeScenarios() sets up mole detours, by switching on the enumeration parameter. This allows the same scenarios to be erected, in a DRY manner, throughout many tests.
using System;
using Microsoft.Moles.Framework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PexMoleDemo;
[assembly: MoledAssembly("PexMoleDemo")]
namespace TestProject1
{
[TestClass()]
public class ProgramTest
{
public enum Scenarios
{
DivideByZero,
MultiplyInsteadOfDivide
}
private void ArrangeScenario(Scenarios scenario)
{
switch (scenario)
{
case Scenarios.DivideByZero:
PexMoleDemo.Moles.MMyMath.AllInstances.rightGet =
instance => { return 0m; };
break;
case Scenarios.MultiplyInsteadOfDivide:
PexMoleDemo.Moles.MMyMath.AllInstances.Divide =
instance => { return instance.left * instance.right; };
break;
default:
throw new NotImplementedException("Invalid scenario.");
}
}
[TestMethod]
[HostType("Moles")]
[ExpectedException(typeof(DivideByZeroException))]
public void Test1()
{
ArrangeScenario(Scenarios.DivideByZero);
var target = new PexMoleDemo.MyClass();
var math = new PexMoleDemo.MyMath() { left = 1, right = 2 };
var left = math.left;
var right = math.right;
var actual = target.GetResults();
}
[TestMethod]
[HostType("Moles")]
public void Test2()
{
ArrangeScenario(Scenarios.MultiplyInsteadOfDivide);
// Perform some sort of test that determines if code breaks
// when values are multiplied instead of divided.
}
[TestMethod]
[HostType("Moles")]
[ExpectedException(typeof(DivideByZeroException))]
public void Test3()
{
ArrangeScenario(Scenarios.DivideByZero);
var target = new PexMoleDemo.MyOtherClass();
var math = new PexMoleDemo.MyMath() { left = 1, right = 2 };
var left = math.left;
var right = math.right;
var actual = target.Divide();
}
[TestMethod]
[HostType("Moles")]
public void Test4()
{
ArrangeScenario(Scenarios.MultiplyInsteadOfDivide);
// Perform some sort of test that determines if code breaks
// when values are multiplied instead of divided.
}
}
}

Storyboard changing property but not reflected in UI

I have this method fired on a button click. MyObj is an extended Control type with two properties CenterX and CenterY with backing dp CenterXProperty and CenterYProperty.
With the animation playing the CenterX and CenterY properties of MyObj are changing, but I can't see any movement of the object.
private void MoveMyObjectsWithAnimation(MyObj item, Point point)
{
Duration duration = new Duration(TimeSpan.FromSeconds(3));
Storyboard sb = new Storyboard();
sb.Duration = duration;
DoubleAnimation da = new DoubleAnimation();
da.Duration = duration;
da.EasingFunction = new SineEase();
DoubleAnimation da1 = new DoubleAnimation();
da1.Duration = duration;
da1.EasingFunction = new SineEase();
Storyboard.SetTarget(da, item);
Storyboard.SetTargetProperty(da, new PropertyPath(MyObj.CenterXProperty));
da.From = item.CenterX;
da.To = point.X;
Storyboard.SetTarget(da1, item);
Storyboard.SetTargetProperty(da1, new PropertyPath(MyObj.CenterYProperty));
da1.From = item.CenterY;
da1.To = point.Y;
sb.Children.Add(da);
sb.Children.Add(da1);
sb.Begin();
}
Following is the property system declared in MyObj class.
public double CenterX
{
get
{
return (double)GetValue(CenterXProperty)+25;
}
set
{
Canvas.SetLeft(this, value - 25);
SetValue(CenterXProperty, value);
OnPropertyChanged(new PropertyChangedEventArgs("CenterX"));
}
}
public double CenterY
{
get
{
return (double)GetValue(CenterYProperty) + 25;
}
set
{
Canvas.SetTop(this, value - 25);
SetValue(CenterYProperty, value);
OnPropertyChanged(new PropertyChangedEventArgs("CenterY"));
}
}
public static readonly DependencyProperty CenterXProperty =
DependencyProperty.Register("CenterX", typeof(double), typeof(MyObj), null);
public static readonly DependencyProperty CenterYProperty =
DependencyProperty.Register("CenterY", typeof(double), typeof(MyObj), null);
What am I doing wrong?
This was a tough one as your code appeared correct.
The problem is actually that the DP setter is not called when a property is animated. Your property setters were never hit.
You can only catch the callback on the property change instead.
This works:
public double CenterX
{
get { return (double)GetValue(CentreXProperty); }
set { SetValue(CentreXProperty, value); }
}
// Using a DependencyProperty as the backing store for CentreX.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty CentreXProperty =
DependencyProperty.Register("CentreX",
typeof(double),
typeof(MyObj),
new PropertyMetadata(0.0,
new PropertyChangedCallback(OnCentreXChanged)));
static void OnCentreXChanged(object sender, DependencyPropertyChangedEventArgs args)
{
// Get reference to self
MyObj source = (MyObj)sender;
// Add Handling Code
double newValue = (double)args.NewValue;
Canvas.SetTop(source, newValue - 25);
}
Just duplicate for your CenterYProperty.
I suggest you get a hold of the SLDP snippet from here and always use that to add DPs. (There is one typo in the code it generates, but the snippet it easy to fix).