Workflow foundation custom Assign Activity - workflow-foundation-4

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.

Related

Binding labels textProperty to object's property held by another final ObjectProperty

In app I'm bulding I used data model presented by James_D here:
Applying MVC With JavaFx
I just can find a way to bind labels text to property of object held in DataModel
Data is structured like this:
model class Student
//partial class
public class Student {
private final StringProperty displayName = new SimpleStringProperty();
public final StringProperty displayNameProperty(){
return this.displayName;
}
public Student(){
}
public final String getDisplayName() {
return this.displayNameProperty().get();
}
public final void setDisplayName(String displayName) {
this.displayNameProperty().set(displayName);
}
}
Student instaces are held by StudentDataModel class
public class StudentDataModel {
// complete student list
private final ObservableList<Student> studentList = FXCollections.observableArrayList();
private final ObjectProperty<Student> selectedStudent = new SimpleObjectProperty<>(new Student());
public final Student getSelectedStudent() {
return selectedStudent.get();
}
public final ObjectProperty<Student> selectedStudentProperty() {
return selectedStudent;
}
public final void setSelectedStudent(Student student) {
selectedStudent.set(student);
}
}
StudentList is displayed by Table View, there is change listener that sets selectedStudent like this:
public class TableViewController {
public void initModel(StudentDataModel studentDM) {
// ensure model is set once
if (this.studentDM != null) {
throw new IllegalStateException("StudentDataModel can only be initialized once");
}
this.studentDM = studentDM;
tableView.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
if (newSelection != null) {
studentDM.setSelectedStudent(newSelection);
}
});
}}
There is another controller ActionsBarController that has label to display selected student (this seems redundant, but there is option for selecting multiple objects to perform bulk operations).
StudentDataModel is initialized properly (I can see it in debuger) but below doesn't do anything:
chosenStudentLabel.textProperty().bind(studentDM.getSelectedStudent().displayNameProperty());
//this shows class name with instance number changing correctly
chosenStudentLabel.textProperty().bind(studentDM.selectedStudentProperty().asString());
I could inject ActionsBarController to TableViewController and change label text from change Listener there, but this seems counter productive with data model.
What am I doing wrong?
Your code doesn't work, because you call (and evaluate) getSelectedStudent() at the time the binding is created (i.e. when you initialize the model). As a consequence, you only bind to the displayName property of the student that is selected at that time. (If nothing is selected, you'll get a NullPointerException.) The binding will only change if that initially-selected student's display name changes; it won't change if the selection changes.
You need a binding that unbinds from the old selected student's display name, and binds to the new selected student's display name, when the selected student changes. One way to do this is:
chosenStudentLabel.textProperty().bind(new StringBinding() {
{
studentDM.selectedStudentProperty().addListener((obs, oldStudent, newStudent) -> {
if (oldStudent != null) {
unbind(oldStudent.displayNameProperty());
}
if (newStudent != null) {
bind(newStudent.displayNameProperty());
}
invalidate();
});
}
#Override
protected String computeValue() {
if (studentDM.getSelectedStudent() == null) {
return "" ;
}
return studentDM.getSelectedStudent().getDisplayName();
}
});
Note that there is also a "built-in" way to do this, but it's a bit unsatisfactory (in my opinion) for a couple of reasons. Firstly, it relies on specifying the name of the "nested property" as a String, using reflection to access it. This is undesirable because it has no way to check the property exists at compile time, it requires opening the module for access, and it is less good performance-wise. Secondly, it gives spurious warnings if one of the properties in the "chain" is null (e.g. in this case if the selected student is null, which is will be initially), even though this is a supported case according to the documentation. However, it is significantly less code:
chosenStudentLabel.textProperty().bind(
Bindings.selectString(studentDM.selectedStudentProperty(), "displayName")
);

D365 Updating condition in method

I have Areas tab which contain grid with some calculations.
That calculations depends from area which is selected.
Situation is next: One object can have several areas, and when I open Areas tab, it calculates good but, when in object I change Area from one to another, value in calculations stays from previous. On the other words: it not get updated. I am using this code:
[Control("TabPage")]
class TabLineAreaGroup
{
public void pageActivated()
{
PMCContractArea contractArea;
AmountMST sumContractArea;
super();
pmcContractLine_ds.readCommonAreas(pmcContractLine);
h1_h2.realValue(pmcContractLine_ds.h1_h2(pmcContractLine));
efa.realValue(pmcContractLine_ds.efa(pmcContractLine));
bfa.realValue(pmcContractLine_ds.bfa(pmcContractLine));
mfa.realValue(pmcContractLine_ds.mfa(pmcContractLine));
sumArea.realValue(h1_h2.realValue() + efa.realValue() + bfa.realValue() + mfa.realValue());
while select AreaSelector, sum(RentalValue)
from contractArea
group by AreaSelector
where contractArea.ContractId == pmcContract.ContractId
&& contractArea.RentalObjectId == pmcContractLine.RentalObjectId
{
sumContractArea += contractArea.RentalValue;
switch (contractArea.AreaSelector)
{
case PMEAreaSelector::CommonAreaBuilding :
contractAreaBFA.realValue(contractArea.RentalValue);
break;
case PMEAreaSelector::CommonAreaSection :
contractAreaEFA.realValue(contractArea.RentalValue);
break;
case PMEAreaSelector::PrimaryArea, PMEAreaSelector::SecondaryArea :
contractAreaH1_H2.realValue(contractArea.RentalValue);
break;
case PMEAreaSelector::CommonAreaFixed :
contractAreaMFA.realValue(contractArea.RentalValue);
break;
}
}
contractAreaSum.realValue(sumContractArea);
}
}
What I need to add in this code, so when area is changed to update the calculations in grid ?
For Dynamics 365, Microsoft sometimes deprecates methods and doesn't update documentation, or they leave methods available, but have not implemented them.
For D365, it's likely you will need to use the event handler method on the Tab control.
Below is a sample where I just created a form with a couple Tab+Grid and the datasource of CustGroup
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
[FormControlEventHandler(formControlStr(TestForm, FormTabControl1), FormControlEventType::TabChanged)]
public static void FormTabControl1_OnTabChanged(FormControl sender, FormControlEventArgs e)
{
// You can interact with FormRun
FormRun formRun = sender.formRun();
// You can interact with the actual control (from event handler)
FormTabControl formTabControl = sender is FormTabControl ? sender as FormTabControl : null;
// You can get events
FormTabControlTabChangedEventArgs formTabControlTabChangedEventArgs = e is FormTabControlTabChangedEventArgs ? e as FormTabControlTabChangedEventArgs : null;
// You can interact with the tab pages
if (formTabControl && formTabControlTabChangedEventArgs)
{
FormControl fc = formTabControl.controlNum(formTabControlTabChangedEventArgs.oldTab());
FormTabPageControl tabPageOld = formTabControl.controlNum(formTabControlTabChangedEventArgs.oldTab());
FormTabPageControl tabPageNew = formTabControl.controlNum(formTabControlTabChangedEventArgs.newTab());
info(strFmt("Tab changed from %1 to %2", tabPageOld.caption(), tabPageNew.caption()));
}
// You can interact with datasources
FormDataSource fdsCustGroup = formRun.dataHelper().FindDataSource('CustGroup');
}

xamarin-forms sqlite multi tables

Cannot work with more than one table. What to do to get to work with two, three or more tables?
Visual Studio >>> Xamarin-Forms
/// I think maybe this code needs to be fixed somehow.
/// this code is placed in App.cs (up)
static Data.TableTwo tabletwo;
static Data.TableOne tableone;
/// crashed
public Task<int> SaveItemAsync(TableTwo item)
{
if (item.ID != 0)
{
return tabletwo.UpdateAsync(item);
}
else
{
return tabletwo.InsertAsync(item);
}
}
/// ***I think maybe this code needs to be fixed somehow.
/// this code is placed in App.cs (down below)
public static Data.TableTwo tabletwo
{
get
{
if (datadistance == null)
{
tabletwo = new Data.TableTwo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "TodoSQLite.db3"));
}
return tabletwo;
}
}
/// ***I think maybe this code needs to be fixed somehow.
/// this code is placed in App.cs (down below)
public static Data.TableOne tableone
{
get
{
if (tableone == null)
{
tableone = new Data.TableOne(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "TodoSQLite.db3"));
}
return tableone;
}
}
above code works correctly. When the code above is called. Application falls.
I have two tables. With one table when the user works (saves and deletes data), then everything works. If the user starts working with another table (save data) the application crashes.
!!!application tree!!!
DataBase(folder)
TableTwo(file)
TableOne(file)
Models(folder)
TableTwo(file)
TableOne(file)
Everything was done according to the code from the article https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/databases#using-sqlite
In fact, I just copied the code a second time, and pasted into the project. - this is what I have done creating the second table and working with it (deleting insert data)
Let's assume your two tables are of types Load and Category and your database is of type MyDatabase
You might want to keep a single connection to SqlLite inside the MyDatabase class and add the methods to interact with your tables as follows:
public class MyDatabase
{
private readonly SQLiteAsyncConnection _connection;
public MyDatabase():this(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "MyDatabase.db3"))
{
}
internal MyDatabase(string dbPath)
{
_connection = new SQLiteAsyncConnection(dbPath);
_connection.CreateTableAsync<Load>().Wait();
_connection.CreateTableAsync<Category>().Wait();
}
public Task<List<Load>> GetLoads() =>
_connection.Table<Load>().ToListAsync();
public Task<List<Category>> GetCategories() =>
_connection.Table<Category>().ToListAsync();
public Task<Load> GetLoad(int id) =>
_connection.Table<Load>().Where(i => i.Id == id).FirstOrDefaultAsync();
public Task<int> SaveLoad(Load item) =>
item.Id != 0 ? _connection.UpdateAsync(item) : _connection.InsertAsync(item);
public Task<int> DeleteLoad(Load item) =>
_connection.DeleteAsync(item);
}
Here is a good sample: https://github.com/xamarin/xamarin-forms-samples/blob/master/Todo/Todo/Data/TodoItemDatabase.cs, but it contains a single table 😊

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 ) );
}

Is it possible to get element.args() from an extended method in Dynamics 365 for operations

In Dynamics 365 for operations I am trying to use a post-event handler of the init() form method from LedgerTransVoucher to find tableId of:
element.args().dataset()
There is a big switch statement in the method that i want to extend with one more case.
What i have done so far is just creating an extension class but i don't really know how to get the data from the extended method
[ExtensionOf(formStr(LedgerTransVoucher))]
final class LedgerTransVoucher_Extension
{
/// <summary>
///
/// </summary>
/// <param name="args"></param>
[PostHandlerFor(formStr(LedgerTransVoucher), formMethodStr (LedgerTransVoucher, init))]
public static void LedgerTransVoucher_Post_init(XppPrePostArgs args)
{
TableId sourceTable;
//sourceTable = element.args().dataset();
//if(sourceTable == tableNum(myTable))
//{
// do something here
//}
}
}
to call:
sourceTable = element.args().dataset();
of course doesn't work but i wan't to know if i can somehow retrieve the data from the extended method.
This can be done very easily with customization but i want to do everything i can to use extensions instead.
Any ideas or possible workarounds?
You can try something like this:
[PostHandlerFor(formStr(LedgerTransVoucher), formMethodStr (LedgerTransVoucher, init))]
public static void LedgerTransVoucher_Post_init(XppPrePostArgs args)
{
TableId sourceTable;
FormRun element;
element = args.getThis();
sourceTable = element.args().dataset();
if(sourceTable == tableNum(myTable))
{
do something here
}
}
or
[FormEventHandler(formStr(LedgerTransVoucher), FormEventType::Initialized)]
public static void LedgerTransVoucher_OnInitialized(xFormRun sender, FormEventArgs e)
{
if(sender.args().dataset() == tableNum(myTable))
{
do something here
}
}

Resources