Databinding/PropertyChanged Notification on object reassignement (Silverlight 4/C#) - data-binding

I have a textblock bound to an object. The 2-way binding works well and as expected.
In the code-behind:
txtNumberOfPlayers.DataContext = tournament.ChipSet;
In the .xaml:
<toolkit:NumericUpDown x:Name="txtNumberOfPlayers" Value="{Binding NumberOfPlayers, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" />
In the Chipset class I raise a change notification when the NumberOfPlayers is set (OnPropertyChanged("NumberOfPlayers");)
But... when I completely reassign the object it does not update the UI unless I call the datacontext assignment again. For example, lets say I load a different chipset object.
Chipset newChipSet = LoadChipset();
tournament.ChipSet = newChipSet;
This does not update the txtNumberOfPlayers when the assignement is made. It only works if I do this:
Chipset newChipSet = LoadChipset();
tournament.ChipSet = newChipSet;
//have to call this again which seems redundant
txtNumberOfPlayers.DataContext = tournament.ChipSet;
So I thought, maybe I have to put the change notification on the Chipset object like this:
private Chipset chipset;
public Chipset ChipSet
{
get { return chipset; }
set
{
if (chipset != value)
{
chipset = value;
OnPropertyChanged("ChipSet");
}
}
}
but that does not work.
So my questions is - how do I get the UI to update when I assign a new object to the old one without rebinding the datacontext.
Thanks!

You should specify RelativeSource to your Binding:
Value={Binding NumberOfPlayers, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type YourNamespace:YourTypeContainingChipsetProperty}}}
EDIT
Example of DependencyProperty in your case. Change YourCustomControl to class name of your control:
public static DependencyProperty ChipsetProperty =
DependencyProperty.Register("Chipset", typeof(Chipset),
typeof(YourCustomControl),
new FrameworkPropertyMetadata
(null,
FrameworkPropertyMetadataOptions
.
BindsTwoWayByDefault, ChipsetPropertyChangedCallback));
public Chipset Chipset
{
get { return (Chipset)GetValue(ChipsetProperty); }
set { SetValue(ChipsetProperty, value); }
}
private static void ChipsetPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var yourCustomControl = d as YourCustomControl;
if (yourCustomControl != null)
{
//your logic on property changed goes here; don't raise OnPropertyChanged!
}
}

Related

Caliburn.Micro Action Parameters for $source

When writing methods for Action Parameters in Caliburn.Micro:
If ActionExecutionContext is the class you use to pass $executionContext, and if KeyEventArgs is the claas you use to pass $eventArgs, then what class do you use to pass $source?
If you know the class of the $source, you could use directly it, either you could use object and test casting.. See a sample with DependencyObject
<Button Content="SelectAll" cal:Message.Attach="[Event Click] = [Action SelectAll($source)]" />
public void SelectAll(object sender)
{
if (sender is DependencyObject)
//something to do
//or
var Grid = (DependencyObject)sender);
}
if you know the class sended:
public void SelectAll(DependencyObject sender)
{
}

Binding to Xamarin Forms FlexLayout.Basis

I have a page in my Xamarin.Forms application where I'm attempting to bind a relative size (25%, 50%, etc) to the basis of an item within a FlexLayout, but can't get it to work successfully.
My XAML:
...
<FlexLayout x:Name="Flex"
BindableLayout.ItemsSource="{Binding Path=SelectedSideBarItem.Items}"
AlignItems="Start"
JustifyContent="Start"
Direction="Row"
AlignContent="Start"
Wrap="Wrap">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Button FlexLayout.Basis="{Binding ItemBasis}"
Title="{Binding Name}"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</FlexLayout>
...
I can't seem to find any examples, and the documentation doesn't seem to mention anything about binding to a Basis property from a view model. My initial attempt was attempting to bind a string to the to the FlexLayout.Basis. Like so:
...
private string _itemBasis;
public string ItemBasis
{
get => _itemBasis;
set
{
_itemBasis = value;
OnPropertyChanged(nameof(ItemBasis));
}
}
...
public void SetBasis()
{
ItemBasis = "25%"; // or any other percentage.
// Does not work!
}
...
The above doesn't work. It appears that the basis is never set, so the default is used.
I should note that binding to a basis does works when using an absolute size (250, 500, etc) (i.e. it's not an issue with my bindings). Like so:
...
private int_itemBasis;
public int ItemBasis
{
get => _itemBasis;
set
{
_itemBasis = value;
OnPropertyChanged(nameof(ItemBasis));
}
}
...
public void SetBasis()
{
ItemBasis = 250; // or any other absolute value.
// Works!
}
...
Any guidance would be appreciate.
Thanks!
The type for the Basis property is FlexBasis
https://github.com/xamarin/Xamarin.Forms/blob/bd31e1e9fc8b2f9ad94cc99e0c7ab058174821f3/Xamarin.Forms.Core/FlexEnums.cs#L58
If you bind it to a string is not going to work (unless using a converter).
Try to modify your bindable property to FlexBasis and assign the value like this:
ItemBasis = new FlexBasis(.5f, true);
The first parameter is the length, the second is the "relative" flag. Setting it to true means that you are using a percentage value.
XAML TypeConverters are not called when a value is set via binding, so the default FlexBasis TypeConverter is not applied when you try to bind the string "25%".
Fortunately the FlexBasisTypeConverter class is public so you can use it in your own converter class like so:
public class StringToFlexBasisConverter: IValueConverter
{
private readonly FlexBasis.FlexBasisTypeConverter _converter = new FlexBasis.FlexBasisTypeConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string stringValue)
{
return _converter.ConvertFromInvariantString(stringValue);
}
return new FlexBasis();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Prism6 shared service and dependency property

I use Prism6 + Unity container for desktop application developing.
This is a long-read, sorry. So I ask at top: Prism SetProperty() function is not rising property changed event if input value is Unity singleton. And I understand why: because input value and save value have same reference to singleton instance. RaisePropertyChanged() don't help in this situation.
Long-read is statring...
So, I have a dependency property in my UserControl component:
public static readonly DependencyProperty WorksheetDataProperty =
DependencyProperty.Register("WorksheetData", typeof(WorksheetDataModel), typeof(SheetUserControl),
new PropertyMetadata(new WorksheetDataModel(), WorksheetDataPropertyChanged));
public WorksheetDataModel WorksheetData {
get { return (WorksheetDataModel)GetValue(WorksheetDataProperty); }
set { SetValue(WorksheetDataProperty, value); }
}
private void WorksheetDataPropertyChanged(WorksheetDataModel worksheetData) {
if (worksheetData == null)
return;
SheetGrid.Model.ActiveGridView.BeginInit();
this.ClearWorksheetModel();
this.ResizeWorksheetModel();
SheetGrid.Model.ActiveGridView.EndInit();
}
private static void WorksheetDataPropertyChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e) {
((SheetUserControl)d).WorksheetDataPropertyChanged((WorksheetDataModel)e.NewValue);
}
It's important for me to invoke actions from WorksheetDataPropertyChanged() function.
And scheme without shared service (singleton) is working well: this function is called.
But now I want to share data between several modules. How I see it: I have some "parent" module, which load\save data from storage and shared this data with several other modules, which can modificate shared data, but can't save it.
And EventAggregator is not convenient for me: I don't want to create copies of data and then collect it again after modifications.
So I register my "shared service" as singleton:
_container.RegisterInstance(new WorksheetDataModel());
Now I can load data from database in "parent" viewmodel to singleton object created in previous step:
var data = _container.Resolve<WorksheetDataModel>();
data.Header = args.Header;
data.User = args.User;
data.RowHeader = new WorksheetRowHeader(_model.ReadRowHeader(data.Header.WshCode));
data.ColHeader = new WorksheetColHeader(_model.ReadColHeader(data.Header.WshCode));
data.Cells = _model.ReadCells(data.Header.WshCode);
Further, I notify child viewmodels about new data in singleton:
data.OnDataChanged?.Invoke();
And now most important code from child viewmodel.
In delegate handler I "apply" new value:
WorksheetData = _container.Resolve<WorksheetDataModel>();
WorksheetData is:
private WorksheetDataModel _worksheetData;
public WorksheetDataModel WorksheetData {
get { return _worksheetData; }
set { SetProperty(ref _worksheetData, value); }
}
And problem in this line:
set { SetProperty(ref _worksheetData, value); }
It works only once at first call, because _worksheetData is null. But then refernce of _worksheetData (pointer) setted to singleton and in all next call value and _worksheetData are identical for SetProperty() and, as result, it just quit.
I tried next code:
set {
SetProperty(ref _worksheetData, value);
RaisePropertyChanged("WorksheetData")
}
But no effect. WorksheetDataPropertyChanged() callback in UserControl component is not calling.
So, I don't know now how to better share some data between several modules.
Thanks for any advice.
WorksheetData does not change, the contents of the WorksheetDataModel instance change.
So to update your bindings,
either WorksheetDataModel implements INotifyPropertyChanged and/or uses INotifyCollectionChanged-implementing collections
or you let the view model listen to WorksheetDataModel.OnDataChanged and raise its own PropertyChanged to update all bindings to WorksheetData.
Example:
private WorksheetDataModel _worksheetData;
public WorksheetDataModel WorksheetData
{
get { return _worksheetData; }
set
{
if (_worksheetData != null)
_worksheetData.OnDataChanged -= DataChangedHandler;
SetProperty(ref _worksheetData, value);
if (_worksheetData != null)
_worksheetData.OnDataChanged += DataChangedHandler;
}
}
private void DataChangedHandler( object sender, DataChangedEventArgs args )
{
RaisePropertyChanged( nameof( WorksheetData ) );
}

Workflow foundation custom Assign Activity

I am defining this in my designer:
<sap:WorkflowItemPresenter>
<statements:Assign DisplayName="Assign"/>
</sap:WorkflowItemPresenter>
I thought it would simply work if i add the Assign there but i was wrong.
[Browsable(false)]
public Activity Body { get; set; }
protected override void Execute(NativeActivityContext context)
{
ActivityInstance res = context.ScheduleActivity(Body, new CompletionCallback(OnExecuteComplete));
}
/// <summary>
/// Called from Execute when Condition evaluates to true.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="instance">The instance.</param>
public void OnExecuteComplete(NativeActivityContext context, ActivityInstance instance)
{
//to be added
}
This is the code from the base class.
I don't need to alter the Assign activity at all, i just want to get access to the NativeActivityContext. In fact i am trying to wrap it up and do some checks on the context's properties in the OnExecuteComplete method. Is there a way to accomplish this?
EDIT 1:
DotNetHitMan suggested and shown me on WF 4 Rehosted Designer - get foreach InArgument Value how to work with those trackings and i indeed succeeded to work this out with his solution:
if (trackingEventArgs.Activity is Assign)
{
Assign ass = trackingEventArgs.Activity as Assign;
if (ass.To.Expression != null)
{
dynamic vbr = null;
if ((ass.To.Expression is VisualBasicReference<int>))
{
//vbr.ExpressionText will hold the value set in the To section of the Assign activity, one of the variables will reside here
vbr = ass.To.Expression as VisualBasicReference<int>;
}
else if ((ass.To.Expression is VisualBasicReference<string>))
{
vbr = ass.To.Expression as VisualBasicReference<string>;
}
ActivityStateRecord activityStateRecord = null;
if (trackingEventArgs.Record != null)
activityStateRecord = trackingEventArgs.Record as ActivityStateRecord;
if (activityStateRecord != null)
{
if (activityStateRecord.Arguments.Count > 0)
{
//checking if the variable defined in the To section is to be displayed in the watch window
GlobalFunctions.WatchWindowViewModel.VariableDefinition existingVariable = GlobalFunctions.WatchWindowViewModel.Instance.VariableExists(vbr.ExpressionText);
if (existingVariable != null)
{
foreach (KeyValuePair<string, object> argument in activityStateRecord.Arguments)
{
if (argument.Key.Equals("Value"))
{
Application.Current.Dispatcher.Invoke(
() =>
{
existingVariable.VariableValue.Clear();
existingVariable.VariableValue.Add(
argument.Value.ToString());
});
}
}
}
}
}
}
}
I still face something a bit ugly. When checking the arguments for the Assign activity i get the key "Value". But if i define a variable named "i" and want to see its changes as this Assign executes i have to take a look at that VisualBasicReference<> to check the name of the variable declared there just like in the code above. This way of doing it works indeed and i managed to cover ints and strings which is fine for now .. but is there any shortcut that can be used in my code ?
EDIT 2
I got a new idea today and put it to work:
Here is the library code:
public sealed class CustomAssign : NativeActivity, IActivityTemplateFactory
{
[Browsable(false)]
public Activity Body { get; set; }
protected override void Execute(NativeActivityContext context)
{
ActivityInstance res = context.ScheduleActivity(Body, new CompletionCallback(OnExecuteComplete));
}
/// <summary>
/// Called from Execute when Condition evaluates to true.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="instance">The instance.</param>
public void OnExecuteComplete(NativeActivityContext context, ActivityInstance instance)
{
//to be added
}
Activity IActivityTemplateFactory.Create(System.Windows.DependencyObject target)
{
return new CustomAssign
{
Body = new Assign()
};
}
}
And the designer:
<sap:ActivityDesigner x:Class="ARIASquibLibrary.Design.CustomAsignDesigner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
xmlns:statements="http://schemas.microsoft.com/netfx/2009/xaml/activities" Collapsible="False" BorderThickness="20" BorderBrush="Transparent">
<sap:ActivityDesigner.Template>
<ControlTemplate TargetType="sap:ActivityDesigner">
<Grid>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</sap:ActivityDesigner.Template>
<DockPanel LastChildFill="True">
<sap:WorkflowItemPresenter Item="{Binding Path=ModelItem.Body, Mode=TwoWay}"/>
</DockPanel>
</sap:ActivityDesigner>
So, in a few words: i've hosted the Assign activity in my custom activity and changed the ControlTemplate in order to keep only the ContentPresenter, which in turn will be the Assign. Now, by dragging it to the designer, you will have exactly the original appearance but with the ability to write code and check the execution steps in the :
protected override void Execute(NativeActivityContext context)
or
public void OnExecuteComplete(NativeActivityContext context, ActivityInstance instance)
Why is that? Through the context.DataContext you can get to all the variables and arguments in the scope where this activity resides in order to develop a watch window.
Rather than dealing with each variable type just convert the expression to its base interface.
ITextExpression vbr = ass.To.Expression as ITextExpression;
You can then just access the expression text property without caring about the type of variable assigned to the expression.
GlobalFunctions.WatchWindowViewModel.VariableDefinition existingVariable = GlobalFunctions.WatchWindowViewModel.Instance.VariableExists(vbr.ExpressionText);
This should cater for (I hope) all variable types that can be applied.

ASP.NET Custom Web control

I'm experiencing some problems and right now I don't know how to solve it. The web control simply updates a clock represented by a label every second. My issue is that the web control exposes a property called 'Formato' where the user can select to display in format 12 or 24 hours. This is done with an enum type where in spanish Doce means 12 and Veinticuatro means 24. This is the code for the server control:
namespace Ejercicio2RelojControl
{
public enum _FormatoHora
{
Doce,
Veinticuatro
}
[DefaultProperty("FormatoHora")]
[ToolboxData("<{0}:Ejercicio2RelojControl runat=server></{0}:Ejercicio2RelojControl>")]
[ToolboxBitmap(typeof(Ejercicio2RelojControl), "Ejercicio2RelojControl.Ejercicio2RelojControl.ico")]
//[Designer("Ejercicio2RelojControl.Ejercicio2RelojControlDesigner, Ejercicio2RelojControl")]
public class Ejercicio2RelojControl : WebControl
{
public Ejercicio2RelojControl()
{
}
[
//Bindable(true),
Category("Appearance"),
//DefaultValue(_FormatoHora.Doce),
Description(""),
]
public virtual _FormatoHora FormatoHora
{
get
{
//object t = ViewState["FormatoHora"];
//return (t == null) ? _FormatoHora.Doce : (_FormatoHora)t;
object obj2 = this.ViewState["_FormatoHora"];
if (obj2 != null)
{
return (_FormatoHora)obj2;
}
return _FormatoHora.Doce;
}
set
{
ViewState["_FormatoHora"] = value;
}
}
//Create one TimerControl
Timer timer = new Timer();
private Label clockLabel = new Label();
// Declare one Updatepanel
UpdatePanel updatePanel = new UpdatePanel();
// Now override the Load event of Current Web Control
protected override void OnLoad(EventArgs e)
{
//Text = "hh:mm:ss";
// Create Ids for Control
timer.ID = ID + "_tiker";
clockLabel.ID = ID + "_l";
// get the contentTemplate Control Instance
Control controlContainer = updatePanel.ContentTemplateContainer;
// add Label and timer control in Update Panel
controlContainer.Controls.Add(clockLabel);
controlContainer.Controls.Add(timer);
// Add control Trigger in update panel on Tick Event
updatePanel.Triggers.Add(new AsyncPostBackTrigger() { ControlID = timer.ID, EventName = "Tick" });
updatePanel.ChildrenAsTriggers = true;
// Set default clock time in label
clockLabel.Text = DateTime.Now.ToString("h:mm:ss tt");
//clockLabel.Text = DateTime.Now.ToString("H:mm:ss");
// Set Interval
timer.Interval = 1000;
// Add handler to timer
timer.Tick += new EventHandler<EventArgs>(timer_Tick);
updatePanel.RenderMode = UpdatePanelRenderMode.Block;
//Add update panel to the base control collection.
base.Controls.Add(updatePanel);
}
protected override void RenderContents(HtmlTextWriter output)
{
output.Write(FormatoHora);
}
void timer_Tick(object sender, EventArgs e)
{
// Set current date time in label to move current at each Tick Event
clockLabel.Text = DateTime.Now.ToString("h:mm:ss tt");
//clockLabel.Text = DateTime.Now.ToString("H:mm:ss");
}
}
}
Now it's time to test the custom control in an asp.net web application.
<cc1:Ejercicio2RelojControl ID="Ejercicio2RelojControl1" runat="server" />
Works great! BUT when I add the property "Formato" fails at compile time:
<cc1:Ejercicio2RelojControl ID="Ejercicio2RelojControl1" runat="server" Formato="Doce" />
Compiler Error Message: CS0117: 'Ejercicio2RelojControl.Ejercicio2RelojControl' does not contain a definition for 'FormatoHora'
Why is the property Formato making the web app crash at compile time?
Thanks a lot.
EDIT:
namespace Ejercicio2RelojControl
{
public enum FormatoHora
{
Doce,
Veinticuatro
}
[DefaultProperty("FormatoHora")]
[ToolboxData("<{0}:Ejercicio2RelojControl runat=server></{0}:Ejercicio2RelojControl>")]
public class Ejercicio2RelojControl : WebControl, INamingContainer
{
public FormatoHora FormatoHora
{
get
{
object obj2 = this.ViewState["FormatoHora"];
if (obj2 != null)
{
return (FormatoHora)obj2;
}
return FormatoHora.Doce;
}
set
{
ViewState["FormatoHora"] = value;
}
}
As you can see I've changed the public property. Now the error has changed. Is the following:
Compiler Error Message: CS0120: An object reference is required for the non-static field, method, or property 'Ejercicio2RelojControl.Ejercicio2RelojControl.FormatoHora.get'
Any help appreciated. Thanks
EDIT 2:
I've discovered that the problem is on the set {}. If I comment it, all is working fine but then I cannot change FormatoHora between 12 and 24 because of is read only due to only get{} is implemented. Any help with the implementation of set{} ?
I am here for giving you the solution:
You are using the same name for the namespace and for the webcontrol (Ejercicio2RelojControl) . Simply change that and your code will work fine.
Hope it helps, despite the fact some years have passed :)

Resources