I need to count how many times i iterate in a flowchart flow, but i need to be able to read and preferably write to the variable in a custom activity.
My current attempt is declaring the var in design view with scope of the entire Flowchart, default value 0 and incrementing using an Assign activity. But i cannot figure out how i can access the variable in a custom activity without resetting it.
My attempt to access the var is something like whats described in the answer here: Declare Variable<T> variable in a CodeActivity in windows workflow 4.0
Only i don't use a default value for the var when declaring though. Still it seems the var is not in any way related to the var i have defined in design view. I have also tried defining it in code only but then i cannot access it in for example a regular Assign activity.
So what can i do to be able to use the var as a "global" variable?
Thanks.
The most intuitive and perhaps correct way of doing it is to pass the variable that you're declaring on Flowchart level to inside your custom activity. Then you can do whatever you want with it's value and return it.
An example of a custom increment activity (this is how Assign activity works too):
public class IncrementActivity : CodeActivity<int>
{
[RequiredArgument]
public InArgument<int> CountVariable { get; set; }
protected override int Execute(CodeActivityContext context)
{
// Do whatever logic you want here
return CountVariable.Get(context) + 1;
}
}
Here it is a sample of usage using a Sequence (the same when using Flowchart):
var countVar = new Variable<int>("count");
var activity = new Sequence
{
Variables =
{
// declare counter variable at global scope
countVar
},
Activities =
{
new WriteLine { Text = new VisualBasicValue<string>(#"""Count: "" & count") },
new IncrementActivity { CountVariable = countVar, Result = countVar },
new WriteLine { Text = new VisualBasicValue<string>(#"""Count: "" & count") },
new IncrementActivity { CountVariable = countVar, Result = countVar },
new WriteLine { Text = new VisualBasicValue<string>(#"""Count: "" & count") },
new IncrementActivity { CountVariable = countVar, Result = countVar }
}
};
Output:
Count: 0
Count: 1
Count: 2
Note that is simpler through visual designer as you don't have to directly use VisualBasicValue<string> to build print string. Other than that, is exactly the same!
Related
I'm using TornadoFX 1.7.5 and I can't seem to get bound properties to work. I have the below ItemViewModels
class DynamicMenuViewModel : ItemViewModel<DynamicMenu>(DynamicMenu()) {
val title = bind { item?.title?.toProperty() }
val isBold = bind { item?.isBold?.toProperty() }
val routes = bind { item?.routes?.toProperty() }
}
data class DynamicMenu(var title: String = "", var isBold: Boolean = false, var routes: MutableList<MenuRouteViewModel> = mutableListOf())
class MenuRouteViewModel : ItemViewModel<MenuRoute>(MenuRoute()) {
val url = bind { item?.url?.toProperty() }
val title = bind { item?.title?.toProperty() }
val isBold = bind { item?.isBold?.toProperty() }
val showNew = bind { item?.showNew?.toProperty() }
}
data class MenuRoute(var url: String = "", var title: String = "", var showNew: Boolean = false, var isBold: Boolean = false)
Which are bound like this:
//routesController.dynamicMenu is an instance of DynamicMenuViewModel()
textfield(property = routesController.dynamicMenu.title) {
prefWidth = formWidth * .5
gridpaneConstraints {
columnRowIndex(0, 1)
marginLeft = 10.0
columnSpan = 2
marginBottom = 20.0
}
}
checkbox(property = routesController.dynamicMenu.isBold){
gridpaneConstraints {
columnRowIndex(2, 1)
marginLeft = 15.0
marginBottom = 20.0
}
}
Then the following functions commit the models and prints them to the screen when I click a button:
fun onClick(){
commitModel()
println(dynamicMenu.item.toString())
dynamicMenu.item.routes.forEach {
println(it.item.toString())
}
}
fun commitModel(){
dynamicMenu.item.routes.forEach {
it.commit()
}
dynamicMenu.commit()
}
The problem is that when I run the program and edit the textfields and checkboxes then click the button that runs onClick(), the backing item doesn't seem to be getting updated. So none of the updated values are printed to the console.
What am I doing wrong here?
The ViewModel can as you probably know only bind bidirectionally against JavaFX Properties. Your domain objects doesn't contain JavaFX properties, so you need to convert them. However, the toProperty() function you are using only operates on a value, and turns it into a Property. This property has no way of knowing about it's field owner, and hence cannot write back into the domain object.
Luckily, you can use the observable function to make your domain object properties writable as well:
val url = bind { item?.observable(MenuRoute::url) }
Since the observable function operates on a specific instance of a MenuRoute object, it now has enough information to write back into that instance when you commit() the model.
If you would consider changing the properties in your domain objects to be observable, you could write:
val url = bind(MenuRoute::url)
You can use the TornadoFX IDEA plugin inspection "Convert all properties to TornadoFX Properties" to automatically rework your properties. This would transform your MenuRoute object into:
class MenuRoute {
val isBoldProperty = SimpleBooleanProperty(false)
var isBold by isBoldProperty
val showNewProperty = SimpleBooleanProperty(false)
var showNew by showNewProperty
val urlProperty = SimpleStringProperty("")
var url by urlProperty
val titleProperty = SimpleStringProperty("")
var title by titleProperty
}
(You have to manually remove the data modifier on your class. Also beware that the current version of the plugin has a bug in the conversion function that would leave the old parameters - a new version will be released shortly).
If you don't want to do that for various reasons, I was just able to support that nice syntax even for mutable vars like you have, so from TornadoFX 1.7.6 you can use this syntax in your binding statements even if you don't want to change your data classes:
val url = bind(MenuRoute::url)
After reading this article:
http://blogs.msdn.com/b/tilovell/archive/2009/12/29/the-trouble-with-system-activities-foreach-and-parallelforeach.aspx
I have defined the ForEachFactory as follows:
public class ForEachFactory<T> : IActivityTemplateFactory
{
public Activity Create(DependencyObject target)
{
return new ForEach<T>
{
DisplayName = "ForEachFromFactory",
Body = new ActivityAction<T>
{
Argument = new DelegateInArgument<T>("item")
}
};
}
}
All works well but is it possible to check how that DelegeateInArgument in my case named "item" changes its value ?
So if i have defined an array in the variables section and initialized with
{1, 2, 3} i need a way to check how the "item" takes value 1, 2 and then 3.
To be more accurate, i've added this pic, with a breakpoint on the WriteLine activity inside the foreach. When the execution will stop there, is there a way to find out what the value of item is ?
EDIT 1:
Possible solution in my case:
After struggling a bit more i found one interesting thing:
Adding one of my custom activities in the Body of the ForEach, i am able to get the value of the item like this :
So, my activity derives from : CodeActivity
Inside the protected override String[] Execute(CodeActivityContext context) i am doing this job.To be honest, this solves the thing somehow, but it is doable only in my custom activities. If i would put a WriteLine there for example, i would not be able to retrieve that value.
you can access the DelegeateInArgument of a ForEach activity by inspecting the ModelItem trees parent and checking for DelegeateInArgument's. If you need a specific code example to achieve this I may need a some time to code the example. As it has been a long time since I did this, see my question i asked over on msdn
So basically where your break point is, you can access the variable values as these are defined with n the scope of your activity as 'variables'. However the 'item' variable is actually only accessible from the parent loop activity. So you have to get the model item of the current executing activity and then traverse up the tree to find the parent containing the desired DelegateInArgument.
Can you flesh out exactly what you want to achieve? Is it that when your debugging the workflow in the re-hosted designer you want to display the variable values to the user as they change in the UI?
Edit - added tracking example
So as your wanting to display the variable values during execution of the workflow we need to use tracking to achieve this. In the example your using the author has already implemented some basic tracking. So to achieve the extended variable tracking you want you will need to alter the tracking profile.
Firstly amend the WorkflowDesignerHost.xaml.cs file alter the RunWorkflow method to define the SimulatorTrackingParticipant as below.
SimulatorTrackingParticipant simTracker = new SimulatorTrackingParticipant()
{
TrackingProfile = new TrackingProfile()
{
Name = "CustomTrackingProfile",
Queries =
{
new CustomTrackingQuery()
{
Name = all,
ActivityName = all
},
new WorkflowInstanceQuery()
{
**States = {all },**
},
new ActivityStateQuery()
{
// Subscribe for track records from all activities for all states
ActivityName = all,
States = { all },
**Arguments = {all},**
// Extract workflow variables and arguments as a part of the activity tracking record
// VariableName = "*" allows for extraction of all variables in the scope
// of the activity
Variables =
{
{ all }
}
}
}
}
};
This will now correctly capture all workflow instance states rather than just Started/Completed. You will also capture all Arguments on each activity that records tracking data rather than just the variables. This is important because the 'variable' were interested in is actually (as discussed earlier) a DelegateInArgument.
So once we have changed the tracking profile we also need to change the SimulatorTrackingParticipant.cs to extract the additional data we are now tracking.
If you change the OnTrackingRecordReceived method to include the following sections these will capture variable data and also Argument data during execution.
protected void OnTrackingRecordReceived(TrackingRecord record, TimeSpan timeout)
{
System.Diagnostics.Debug.WriteLine(
String.Format("Tracking Record Received: {0} with timeout: {1} seconds.", record, timeout.TotalSeconds)
);
if (TrackingRecordReceived != null)
{
ActivityStateRecord activityStateRecord = record as ActivityStateRecord;
if (activityStateRecord != null)
{
IDictionary<string, object> variables = activityStateRecord.Variables;
StringBuilder vars = new StringBuilder();
if (variables.Count > 0)
{
vars.AppendLine("\n\tVariables:");
foreach (KeyValuePair<string, object> variable in variables)
{
vars.AppendLine(String.Format(
"\t\tName: {0} Value: {1}", variable.Key, variable.Value));
}
}
}
if (activityStateRecord != null)
{
IDictionary<string, object> arguments = activityStateRecord.Arguments;
StringBuilder args = new StringBuilder();
if (arguments.Count > 0)
{
args.AppendLine("\n\tArgument:");
foreach (KeyValuePair<string, object> argument in arguments)
{
args.AppendLine(String.Format(
"\t\tName: {0} Value: {1}", argument.Key, argument.Value));
}
}
//bubble up the args to the UI for the user to see!
}
if((activityStateRecord != null) && (!activityStateRecord.Activity.TypeName.Contains("System.Activities.Expressions")))
{
if (ActivityIdToWorkflowElementMap.ContainsKey(activityStateRecord.Activity.Id))
{
TrackingRecordReceived(this, new TrackingEventArgs(
record,
timeout,
ActivityIdToWorkflowElementMap[activityStateRecord.Activity.Id]
)
);
}
}
else
{
TrackingRecordReceived(this, new TrackingEventArgs(record, timeout,null));
}
}
}
Hope this helps!
Hi guys looking for some basic advice.
I have four models: BoardViewModel, List, Card, Member
var Member = function (id, name, avatar) {
var self = this;
self.id = id;
self.name = name;
self.avatar = avatar;
self.isChecked = ko.observable(false);
};
I am instantiating members property inside BoardViewModel. But I want to use a copy of this model inside each Card model to instantiate a list of assigned members.
Each card stores comma separated list of member references like
",1,2,4,5"
I am writing a loop to BoardViewModel.members and mark members as checked if id references match bore I assign it as Card.members.
The last piece of the puzzle I am missing is reference to the BoardViwModel.members.
I have a lovely example fiddler that would somewhat help to build a picture of what I am talking about.
Just bear in mind that once I have this working properly I want to replace view() binding
foreach: $root.members
with
foreach: members
If at all possible I would like to avoid passing BoardViewModel.members as parameter into List and then into Card.
Update 1
As suggested by #Jeroen here's a simplified version of my fiddler.
The top view() model which encompases a concept of lists:
var BoardViewModel = function (lists, members) {
var self = this;
// in reality members are fetched via ajax call to the server
// and instantiate as a ko.observableArray()
self.groupMembers = ko.observableArray(members);
self.lists = ko.observableArray(lists);
...
}
In reality this has a signature like this:
var boardViewModel = function (initialData)
moving on.
The child List model which encompases a concept of cards:
var List = function (id, name, cards, sortOrder, groupMembers) {
var self = this;
self.cards = ko.observableArray(cards);
...
}
in reality:
var list = function (item, groupMembers)
nothing special there really.
The child Card model which encompases the concept of card items (but lets not go there yet):
var Card = function (id, title, description, contentItemId, members, groupMembers) {
var self = this;
self.members = ko.observableArray(parseMembers(members));
// now remember each card has a members property
// which stores comma separated list ",1,4"
function (members) {
var memberList = groupMembers;
var memberRefList = members.split(',');
ko.utils.arrayForEach(memberList, function(member){
ko.utils.arrayForEach(memberRefList, function(memberId){
if(member.id === meberId) {
member.isChecked(true);
}
});
});
return memberList;
}
...
}
in reality:
var card = function (item, groupMembers)
nothing too fancy there either.
I currently have something like this working on my dev environment.
Problem:
Those with keen eyes probably noticed the way I was passing groupMembers all the way up. I am not particularly hyped about the idea.
Anyone know a better way of implementing this? i.e. why can't I just do something like
var memberList = self.parent.parent.groupMembers;
for instance.
As per me, the better way to do is to have the child viewmodels inside the parent view-model. like this where you can access the parent data members as well as methods directly.
ViewModel
var BoardViewModel = function(){
var self = this;
this.members = ko.observableArray();
this.lists = ko.observableArray();
// Child View Models
this.Member = function(member){
this.id = member.id;
this.name = member.name;
this.avatar = member.avatar;
this.isChecked = ko.observable(false);
}
this.List = function(list){
// same for this
};
this.Card = function(card){
// same for this
};
// a Method to bind the data with the observables and arrays
// Assuming data is a json object having Members, List objects
this.applyData = function(data){
self.members(jQuery.map(data.Members, function(item){
return new self.Member(item);
}));
self.lists(jQuery.map(data.Lists, function(item){
return new self.List(item);
}));
}
}
onDom ready
// json object holding your data
var data = {
"Members" : [
],
"Lists" : [
],
"Cards" : [
]
};
var vm = new BoardViewModel();
$(function(){
ko.applyBindings(vm, document.getElementById('myModule'));
vm.applyData(data);
});
I am working on an ASP.Net MVC app and I want to show a confirmation page after the user edits some data. What I would like to show is a list of the pending changes that the user made to the model.
For example,
Are you sure you want to make the following changes:
FieldName:
Previous Value: XXX
New Value: YYY
I know I can read my stored value from the database and compare it with the POSTed object but I want this to work generally. What would be some good ways to approach this?
To clarify, I am looking for a general way to get a "diff" of the pending changes. I already know how to get the previous and pending changes. Kind of like how TryUpdateModel() can attempt to update any Model with posted values. I'd like a magical GetPendingModelChanges() method that can return a list of something like new PendingChange { Original = "XXX", NewValue = "YYY"} objects.
You might be doing this already but I wouldn't send my model to the view, create a viewmodel. In this case I would map the model data to the viewmodel twice, my viewmodel might contain OrderInput and OrderInputOrig. Then stick OrderInputOrig in hidden fields. On post back you can compare the values and then redirect, if something changed, to a display view with the original and the changes for confirmation.
Maybe something like this:
[HttpPost]
public ActionResult Edit(CustomerInput cutomerInput)
{
var changes = PublicInstancePropertiesEqual(cutomerInput.OriginalCustomer, cutomerInput.Customer);
if (changes != null)
{
cutomerInput.WhatChangeds = changes;
return View("ConfirmChanges", cutomerInput);
}
return View();
}
public ActionResult ConfirmChanges(CustomerInput customerInput)
{
return View(customerInput);
}
from: Comparing object properties in c#
public static Dictionary<string, WhatChanged> PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
{
Dictionary<string, WhatChanged> changes = null;
if (self != null && to != null)
{
var type = typeof(T);
var ignoreList = new List<string>(ignore);
foreach (System.Reflection.PropertyInfo pi in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
{
if (!ignoreList.Contains(pi.Name))
{
var selfValue = type.GetProperty(pi.Name).GetValue(self, null);
var toValue = type.GetProperty(pi.Name).GetValue(to, null);
if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
{
if (changes == null)
changes = new Dictionary<string, WhatChanged>();
changes.Add(pi.Name, new WhatChanged
{
OldValue = selfValue,
NewValue=toValue
});
}
}
}
return changes;
}
return null;
}
Coming in very late here, but I created a library to do this on MVC models and providing "readable" diffs for humans using MVC ModelMetadata:
https://github.com/paultyng/ObjectDiff
It gives me output when I save a Model similar to:
Status: 'Live', was 'Inactive'
Phone: '123-456-7898', was '555-555-5555'
Etc.
use the TempData Dictionary.
TempData["previousValue"];
TempData["newValue"];
I have two questions regarding the Flex combo box.
The string representing the function name will be read from xml # run time.
var combo:ComboBox = new ComboBox();
combo.labelFunction = "functionName";
How can I achieve this?
So the first name, which is to be displayed in the combo box, can be only retrieved by accessing another DTO, called person and then its first name.
var combo:ComboBox = new ComboBox();
combo.labelField= "person.firstName";
My class looks like this,
public class Test
{
public var person:PersonDTO;
}
public class PersonDTO
{
public var firstName:String;
}
Is it possible to access any multi-level text using the combo box label field ?
You need to pass the function not the name.
Doing this
combo.labelFunction = "functionName";
Is passing a string.
The only work around I can think of is to make a switch statement with one case for each function you may have. Then call that with "case" from within your xml.
switch( xml.#labelfunction ){
case 'func1':
combo.labelFunction = this.func1;
break;
case 'func2':
combo.labelFunction = this.func2;
break;
}
Its hacky but should work.
ad 1) labelFunction
Calling functions when you know only the name as String is quite easy. The following snippets shows how you can execute a function that is a member of the same class. In case you need to call a function from another class replace this with the according variable name.
private function comboBox_labelFunction(item:Object):String
{
var functionName:String = myXml.#labelFunctionName;
return this[functionName](item);
}
ad 2) labelField
It's normally not possible to use "person.firstName" as labelField. However, you should be able use it within your labelFunction. Something like this should work...
private function comboBox_labelFunction(item:Object):String
{
var labelField:String = "person.firstName";
var attributeNames:Array = labelField.split(".");
for each (var attributeName:String in attributeNames)
{
if (item && item.hasOwnProperty(attributeName))
item = item[attributeName];
else
return null;
}
return item;
}