Passing variable through context.ScheduleActivity() in custom WPF component - c#

I have created an activity to mimic the sequence activity in WPF using the below code with help from Windows Workflow Custom Sequence Activity
using System.Activities;
using System.Activities.Statements;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace Custom_Activities
{
[Designer("System.Activities.Core.Presentation.SequenceDesigner, System.Activities.Core.Presentation")]
public class Scoped_Activity_Scope : NativeActivity
{
private string TestVariable = "testing testing";
private Sequence innerSequence = new Sequence();
[Browsable(false)]
public Collection<Activity> Activities
{
get
{
return innerSequence.Activities;
}
}
[Browsable(false)]
public Collection<Variable> Variables
{
get
{
return innerSequence.Variables;
}
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
metadata.AddImplementationChild(innerSequence);
}
protected override void Execute(NativeActivityContext context)
{
System.Console.WriteLine("Scope Executing");
context.ScheduleActivity(innerSequence);
}
}
}
I also have created a second custom activity below
using System.Activities;
namespace Custom_Activities
{
public sealed class Scoped_Activity : Scoped_Activity_Template
{
protected override void Execute(CodeActivityContext context)
{
System.Console.WriteLine("Scope Activity Executing");
//System.Console.WriteLine(testVariable);
}
}
}
When I run the code as below
I get the desired output
Scope Executing
Scope Activity Executing
How can I pass the variable testVariable from the class Scoped_Activity_Scope for use within Scoped_Activity as per the line of code commented out?

First you create a property for the inner activity:
public string TestVariable { get; set; }
Then you can access that property through the Activities collection.
There are 2 situations where you need to update the value of TestVariable: 1, When TestVariable changes, and 2, When you add a new Scoped_Activity to Scoped_Activity_Scope.
Number 1 is easy, just change all Scoped_Activity.TestVariables every time you change the parent's TestVariable. Number 2 is a little more difficult. You need to be able to catch the CollectionChanged event which is fired every time you change Activities from the UI. This is possible through the designer of the activity. I assume you have probably written a custom designer for the activity.
public Scoped_Activity_Scope_Designer()
{
InitializeComponent();
this.Loaded += AddCollectionChangedHandler;
}
private void AddCollectionChangedHandler(object sender, RoutedEventArgs e)
{
var ownactivities =
ModelItem.Properties[nameof(Scoped_Activity_Scope.Activities)].Collection;
ownactivities.CollectionChanged += AddTestVariable;
}
Inside AddTestVariable, just update the TestVariable property of every activity in ownActivities, which you need to retrieve again.
As an aside, you can also access TestVariable by calling the Parent, but then again, you still need to do it inside of Scoped_Activity_Designer, which is the one that actually has access to the parent. However, Scoped_Activity_Designer doesn't know when TestVariable has been updated, and it doesn't know when execute is called in order to retrieve the most up to date value. Using CollectionChanged is probably the best way to do it.
This is how you access TestVariable through the parent:
var dataContext =
Parent.GetValue(Scoped_Activity_Scope_Designer.DataContextProperty);
if (dataContext != null)
{
var designer = (Scoped_Activity_Scope_Designer) dataContext;
var scoped_activity_scope = (Scoped_Activity_Scope) designer.ModelItem.GetCurrentValue();
var scoped_activity = (Scoped_Activity) ModelItem.GetCurrentValue();
scoped_activity.TestVariable = scoped_activity_scope.TestVariable;
}
You should put this code in the handler for the Loaded event inside Scoped_Activity_Designer.

In the Scoped_Activity_Scope within the Execute method I added the following code to iterate through the activities within innerSequence
foreach(Activity a in innerSequence.Activities)
{
if (a.GetType().IsSubclassOf(typeof(UiPath_Activities_Templates.Scoped_Activity_Template)))
{
Scoped_Activity_Template vet = null;
vet = (Scoped_Activity_Template) a;
vet.UpdateTestVariable("changed");
}
}
And in the class 'Scoped_Activity_Template' which 'Scoped_Activity' inherits from (this class was previously empty) I added the following code
public static string TestVariable = "testing";
public void UpdateTestVariable(string newValue)
{
TestVariable = newValue;
}
That way the inherited class Scoped_Activity has access to the variable TestVariable.
When the loop iterates over the activities in innerSequence it checks if it inherits from Scoped_Activity_Template it calls the method UpdateTestVariable to update the variable.
When the class Scoped_Activity is then executed it will have the updated variable.

Related

Using a constructor to fill a list overwrites previous information

I'm still pretty new to C# so I do not know about all the possibilities that it has.
My code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Test ctest = new Test();
private void button1_Click(object sender, EventArgs e)
{
ctest.name = Convert.ToString(textBox1.Text);
MessageBox.Show(ctest.name);
ctest.namelist.Add(ctest);
listBox1.DataSource = null;
listBox1.DataSource = ctest.namelist;
}
}
class Test
{
private string Name;
public string name
{
get { return Name; }
set { Name = value; }
}
public List<Test> namelist = new List<Test>();
public override string ToString()
{
string spaceinbetween = new string(' ', 3);
return name + spaceinbetween + name;
}
What I'm trying to do:
So I want to use my constructor to fill up my listbox with information (name in this case). However it overwrites previous values after adding more then 2 names.
I assume this goes wrong because the class object name gets overwritten and is used in both of the lines that where added.
Question: Is there anyway that adding a new name does not change the previous list values or is there any way to make a new instance of the constructor anytime I wanne create a new ctest?
If I didn't misunderstand your problem, there are a lot of things to change to make your form work properly:
public partial class Form1 : Form
{
// BindingList is perfect here... istead of List. It automatically refreshes upon change.
private BindingList<Test> m_Tests;
public Form1()
{
InitializeComponent();
m_Tests = new BindingList<Test>();
listBox1.DataSource = m_Tests;
// DisplayMember and ValueMember should be defined too here.
}
private void button1_Click(object sender, EventArgs e)
{
// Create a new instance every time the button is clicked.
// Since textBox1.Text is already a String... no need to convert it!
// Thanks to BindingList, you don't need to refresh your binding...
m_Tests.Add(new Test(textBox1.Text));
}
}
public class Test
{
private String m_Name;
public String Name
{
get { return m_Name; }
set { m_Name = value; }
}
public Test(String name)
{
m_Name = name;
}
public override String ToString()
{
if (String.IsNullOrWhiteSpace(m_Name))
return "NO NAME";
return (m_Name + " " + m_Name);
}
}
Let's summarize my changes:
The list should not be part of your Test class, but of your Form1. This is where new instances should be properly added and managed, on a OOP point of view.
Since you are binding the list to a ListBox control, it's better to use BindingList (reference here). It was made exactly for this purpose and it can automatically handle changes.
The BindingList can be initialized when the Form1 constructor is called and its linkage to the ListBox control must be defined as well within the same context. Don't forget to valorize listBox1.DisplayMember and listBox1.ValueMember properties too in order to set up a proper display of your underlying data.
When the button is clicked, all you have to do is to create a new instance of your Test class, with its name defined by textBox1.Text, and add it to the BindingList.
With your previous approach, since a single Test class instance was created within the Form1 class, you were attempting to add the exact same instance every time your button was clicked... and that was producing basically nothing.
You can define a custom constructor for your Test class that accepts the name as argument, to facilitate the creation of new instances.
Properly handle your Test.ToString() override in order to avoid problems.
Overall, use the correct naming conventions to improve your code readability.

Control on another form is inaccessible due to it's protection level

TF2SelectDir.txtTF2DirSelect.Text = "";
This is giving me issues, as txtTF2DirSelect is on one form and I'm trying to change it from another. I tried looking it up, and the entire form itself is already public, not private.
Or, to go along with this, how can I create a variable that can be accessed on any form?
Where it goes wrong
if (canFindTF2 == true)
{
TF2SelectDir.txtTF2DirSelect.Text = "";
The form where TF2SelectDir is is already public
public partial class TF2SelectDir : Form
{
public TF2SelectDir()
{
InitializeComponent();
}
Any ideas? Thanks!!
UPDATE
At the bottom of my TF2SelectDir.Designer.cs, I've found
private System.Windows.Forms.TextBox txtTF2DirSelect;
private System.Windows.Forms.Button btnSaveTF2Dir;
private System.Windows.Forms.Label lblExample;
However, when I changed private to public on txtTF2DirSelect, I got a new error.
"An object reference is required for the non-static field, method, or property 'TF2SelectDir.txtTF2DirSelect' - Error Code CS0120
Since I cannot comment, I am posting this as an answer.
Accessing controls from a separate form, may not be the best idea. I would recommend you use properties. Here is Microsoft's definition and usage example of properties.
Another, even better way, in my opinion, to share data between two forms, is to use events. Once again, here is Microsoft's description of events.
If you need a working example of how to use either of these approaches, I would like to see your effort first and then we can build on that.
Expose control in below way .. why?? #monstertjie_za provided few good links on that already .
namespace TF2Overwatch
{
public partial class TF2SelectDir : Form
{
//Approch 1 - usable when the projects most works are done
//without following a good architecture
//You can use a static variable to preserve the state and intilize each time
//a new instance is created
//Approch 2 - Responibilty of preserving text to initlize in textbox should be taken
//by the form who calling this form
//value will pass by consturtor or by exposing property
//all approch 2 code are kept commented for better understanding
private static string strTxtTF2DirSelectTextToInitize;
public TF2SelectDir()
{
InitializeComponent();
txtTF2DirSelect.Text = strTxtTF2DirSelectTextToInitize;
}
public static string TxtTF2DirSelectTextToInitlize
{
get
{
return strTxtTF2DirSelectTextToInitize;
}
set
{
strTxtTF2DirSelectTextToInitize = value;
}
}
//public TF2SelectDir(string txtTF2DirSelectText)
//{
// InitializeComponent();
// txtTF2DirSelect.Text = txtTF2DirSelectText;
//}
//public string TxtTF2DirSelectTextToInitlize
//{
// get
// {
// return txtTF2DirSelect.Text;
// }
// set
// {
// txtTF2DirSelect.Text = value;
// }
//}
}
public partial class SomeAnotherForm:Form
{
public SomeAnotherForm ()
{
InitializeComponent();
}
protected void InSomeAction(object Sender, EventArgs e)
{
if (canFindTF2 == true)
{
TF2SelectDir.TxtTF2DirSelectText = "Test";
TF2SelectDir t1 = new TF2SelectDir();
t1.Show();
//Approch 2
//TF2SelectDir t1 = new TF2SelectDir("Test");
//t1.Show();
//TF2SelectDir t1 = new TF2SelectDir();
//t1.TxtTF2DirSelectText="Test"; //look here TxtTF2DirSelectText is setting on instance not by class
//t1.Show();
}
}
}
}

C# Events handling problem for base and derived classes

I have this case where I'm creating 2 different event handlers placed in a base class and subscribing to them accordingly from Quotes and Charts classes. Problem I'm having is that the first subscription triggers fine for the first event but any following subscriptions don't get executed. I have included an example of 2 different handlers, Quotes and Charts, Quotes executes first time with no problems, but Charts does not trigger when data is received.
Base Class:
public abstract class MyBaseClass
{
protected virtual void RaiseOnQuoteData(string item) { }
protected virtual void RaiseOnChartData(string item) { }
void OnDataReceived(object sender, DataEventArgs e)
{
if (e.Item == "QUOTE")
RaiseOnQuoteData(e.Item);
else if (e.Item == "CHART")
RaiseOnChartData(e.Item);
}
}
Quote and Chart Classes:
public class Quote : MyBaseClass
{
public event EventHandler<DataEventArgs<quoteRecord>> OnQuoteData;
protected override void RaiseOnQuoteData(string item)
{
OnQuoteData.Raise<DataEventArgs<quoteRecord>>(this, new DataEventArgs<quoteRecord>(item));
}
}
public class Chart : MyBaseClass
{
public event EventHandler<DataEventArgs<chartRecord>> OnChartData;
protected override void RaiseOnChartData(string item)
{
OnChartData.Raise<DataEventArgs<chartRecord>>(this, new DataEventArgs<chartRecord>(item));
}
}
Subscription:
public class QuoteSubscription
{
public static void SubscribetoQuoteData()
{
Quote Q = new Quote();
Q.OnQuoteData += new EventHandler<DataEventArgs<quoteRecord>>(q_OnQuoteData);
}
static void q_OnQuoteData()
{
//Executes fine
}
}
public class ChartSubscription
{
public static void SubscribetoChartData()
{
Chart C = new Chart();
C.OnChartData += new EventHandler<DataEventArgs<chartRecord>>(q_OnChartData);
}
static void q_OnChartData()
{
//Does not execute
}
}
This is implemented in ASP.NET 4.0, Is there any chance that instantiating the derived classes could be the problem since both classes do share the same base class? Any help pointing to the cause would be greatly appreciated.
What is there in Raise? This must be an extension method, since EventHandler per se doesn't define such a method. Therefore, you can put a breakpoint inside and see, what's going on. (And you could perhaps put a breakpoint inside RaiseOnChartData as well.)
Could it be that you are creating the object within the method scope and has gone out of scope. You may get the first message by coincidence just because it hasn't been GC-ed. Try creating the quote and chart objects as static class member object

Propagating a "volatile" property

I put "volatile" because it's only vaguely so.
I have a class which has a property called StopRequested. This flag can be set by other threads at any time, and needs to indicate to my code that it should stop what it's doing and exit (this is a Windows Service based process, and when Stop is called, all processing needs to clean up and stop).
I wish to create some other classes to do the actual brunt of the processing work, however these classes also have to be aware of the "stop" flag. You can't just pass the flag because it will pass a copy, and you can't pass properties as ref types.
So how do you propagate a property that might change at any time into other classes?
The only thing I can think of is to pass a reference to the parent class, but I dislike coupling the worker classes to the parent for one flag. Any suggestions would be appreciated.
EDIT:
Here's a basic example:
public class A
{
public bool StopRequested { get; set; }
private Worker = new Worker();
public void DoWork();
{
worker.DoWork();
}
}
public class Worker
{
public void DoWork()
{
while(!StopRequested)
{
....
}
}
}
You could have each of your worker classes have their own StopRequest property and then just set that whenever StopRequest is flagged.
private List<IStopable> WorkerClasses = new List< IStopable > ()
public Bool StopRequest{
get
{
return _stopRequest;
}
set
{
_stopReqest = value;
foreach (var child in WorkerClasses)
child.StopRequest = value;
}
}
Like Rubens said, use an event. What you described basically defines event to a T:
Propagate a property change to other classes.
There is actually a facility in .NET that provides this already, albeit in a generic way: INotifyPropertyChanged. This interface provides a single event, PropertyChanged, that allows a class to notify any listeners of any property change.
In your case, you could easily provide your own interface that is more specific:
interface IStopNotifier
{
event EventHandler StopRequested;
}
This interface would be implemented by your main work manager (whatever it is), and could propagate itself like so:
class WorkManager: IStopNotifier
{
public event EventHandler StopRequested;
protected void OnStopRequested()
{
if (StopRequested != null) StopRequested(this, new EventArgs());
}
public void StopAllWorkers()
{
OnStopRequested();
}
public Worker CreateWorker<T>()
where T: Worker
{
var worker = new T(this);
return worker;
}
}
class abstract Worker: IDisposable
{
public Worker(IStopNotifier stopNotifier)
{
stopNotofier.StopRequested += HandleStopRequested;
}
private IStopNotifier m_stopNotifier;
private bool m_stopRequested = false;
internal void HandleStopRequested(object sender, EventArgs e)
{
m_stopRequested = true;
}
public void Dispose()
{
m_stopNotifier.StopRequested -= HandleStopRequested;
}
}
Why don't to create an event to handle stop requests?

WPF: Loading data in the constructor of a UserControl breaks the Designer

I have a main window with a usercontrol. When adding code in the default constructor of the usercontrol, the designer stops showing the main window. It gives a message:
Problem loading
The document contains errors that must be fixed before the designer can be loaded.
Reload the designer after you have fixed the errors.
Reload the designer
Why is this?
This is the code that I have in the constructor:
using (var context = new Data.TVShowDataContext())
{
var list = from show in context.Shows
select show;
listShow.ItemsSource = list;
}
If I can't use the constructor to fill gui with data, when should I else do it? Would it be better to do this with binding? Any sugestions how?
The WPF designer will execute the constructor on child elements when displaying them. My guess is that you've got code in the constructor that throws an exception at design-time, probably because it's using an object that's only available at run-time. A solution would be to surround your constructor logic with a check to prevent it from executing while it's being displayed in the designer.
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
using (var context = new Data.TVShowDataContext())
{
var list = from show in context.Shows
select show;
listShow.ItemsSource = list;
}
}
I would personally use an observable collection for my datasource. There are lots of examples out there, but essentially your code would look something like this. I haven't tested this code. Add some comments if you have any problems.
There are two main points here. One, don't load any data unless your not in design mode, (you could put and else statement and load stub POCO data if you need design support). Two, you should load your data on a seperate thread then your UI thread.
Updated
There was a couple of updates to the code. I changed the (new Thread) to using QueueUserWorkItem, I changed the AddItems method because ObservableCollection doesn't support AddRange, and I changed the spelling on IEnumerable
public class TvShowsDataSource
{
public ObservableCollection<Show> Shows { get; set; }
private void AddItems(IEnumerable<Show> shows)
{
foreach(var show in shows)
Shows.Add(show);
}
public void LoadShowsAsync(Dispatcher dispatcher)
{
ThreadPool.QueueUserWorkItem((state) =>
LoadShows(dispatcher));
}
private void LoadShows(Dispatcher dispatcher)
{
if (dispatcher == null)
throw new ArgumentNullException("dispatcher");
using (var context = new Data.TVShowDataContext())
{
var list = from show in context.Shows
select show;
dispatcher.Invoke(AddItems(list));
}
}
}
public class UserControl1
{
private readonly TvShowsDataSource tvShowsDataSource;
public UserControl1() : this(new TvShowsDataSource()) {}
public UserControl1(TvShowsDataSource tvShowsDataSource )
{
InitializeComponent();
this.tvShowsDataSource = tvShowsDataSource;
listShow.ItemsSource = tvShowsDataSource.Shows;
this.Loaded += UserControl1_Loaded;
}
public void UserControl1_Loaded(object sender, RoutedEventArgs e)
{
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
tvShowsDataSource.LoadShowsAsync(this.Dispatcher);
}
}
}

Categories