I have trying to build a simulator of Ingenico POS terminal (iWL220).
The main screen I have a combo-box. Once user enter id and password the combo-box load 6 menus. If User click btn1 then combo-box clear the menu and add another set of menu. If user click btn1 for new loaded menu then again combo-box cleared and load another set of menu so on.
My problem is for each button click (btn1, btn2, btn3, btn4, btn5) I have to code lot of if else statement. Example;
First menu (on combo-box) has 6 sector.
1.SectorA
2.SectorB
3.SectorC
4.SectorD
5.SectorE
6.SectorF
If user choose 1.SectorA then user click btn1. Then btn1 clear the combo-box and loads another set of menu. This time menu (on combo-box) has 3 companies.
1.CompanyA
2.CompanyB
3.CompanyC
This time user choose 1.CompanyA then user click again btn1. Then btn1 clear the combo-box and loads another set of menu. This time menu (on combo-box) has 2 payment option.
1.FullPayment
2.ParitalPayment
Now this time if user click btn1 or btn2 the combo-box visible become false and in main screen there is a label and text box. The text box allows user to enter the subscriber number and press enter (green button).
I already load Ingenico terminal picture as jpeg and top of it I set my buttons.
I gave only small version of my simulation. In my app there are 114 probability that user can choose.
In my app btn1 has 92 probability to be clicked, btn2 has 53 probability to be clicked and so on. After user enters the subscriber number and click green button the my app use wfc services to format the data and send to sql server.
But before user click each combination of button some where in my app I store the btn number as 422. This 422 means, user chose SectorD + CompanyB + ParitalPayment option. So my wfc will know what it is mean 422.
My question is what is the shortest way to construct my button events for this 114 probability case?
I have 4 buttons. Btn1, Btn2, Btn3 and Btn4. Also I have some arrays as it shown below and 1 combo-box.
1.ArrayMain() = {“1.Water”,”2.Air”,”3.Soil”,”4.Fire”}
1.1. ArrayWater() = {“1.Salty”,”2.Fresh”, “3.Contaminated”}
1.1.1.ArraySalty() = {1.”AA”, 2.”BB”, 3.”CC”}
1.1.2.ArrayFresh() = {1.”DD”, 2.”EE”, 3.”FF”}
1.1.3.ArrayContaminated() = {1.”XX”, 2.”YY”, 3.”ZZ”}
1.2 ArrayAir() = {“1.Fresh”, “2.Contaminated”}
1.3 ArraySoil() = {“1.Normal”, “2.Contaminated”}
1.4 ArrayFire() = {“1.Low”,”2.Mid”,”3.High”}
When my app starts, first array values 1.(ArrayMain) fills the comboBox. This comboBox will have 4 values as, “1.Water”, ”2.Air”, ”3.Soil”, ”4.Fire” in it. If user choose “1.Water” than user clicks Btn1. Than btn1 events clears the comboBox and loads 1.1ArrayWater() values into comboBox.
Second time if user chooses “1.Salty” than user clicks again btn1 and this time btn1 events clears the comboBox and loads 1.1.1ArraySalty() values into comboBox.
Third time if user chooses “2.BB” than user clicks Btn2 and sends the information “BB” for calculation.
First you have 5 (more or less) menu item and each time you press any (number) buttons (1 to 9 lilke in pos terminal) pressed than new menu appears on the screen.
Each button at any specific time shall execute some specific action depending on the state of the system. Obviously, if you try to decide the specific action depending on the multitude of different variables, you will create a lot of branching code. Such code is very difficult to write correctly and even more difficult to debug and maintain.
So, what if we encapsulate current action for each possible state(sequence of state) in some specific class (interface):
/// <summary>
/// Represents internal terminal presenter that is used inside IGlobalTerminalPresenter.
/// </summary>
public interface ITerminalPresenter
{
void UpdateUI();
ITerminalPresenter this[Int32 index]
{
get;
}
ITerminalPresenter Do1();
ITerminalPresenter Do2();
ITerminalPresenter Parent
{
get;
set;
}
void Reset();
}
Inside the form we will use field of a similar interface that will encapsulate all changes of the presenter.
/// <summary>
/// Represents terminal presenter that UI can operate upon.
/// </summary>
public interface IGlobalTerminalPresenter
{
void UpdateUI();
void Do1();
void Do2();
Int32 SelectedIndex
{
get;
set;
}
void Reset();
}
Our event handlers will become:
private void comboBox_SelectedIndexChanged(object sender, EventArgs e)
{
var senderComboBox = (ComboBox)sender;
this.globalTerminalPresenter.SelectedIndex = senderComboBox.SelectedIndex;
}
private void button1_Click(object sender, EventArgs e)
{
this.globalTerminalPresenter.Do1();
}
private void button2_Click(object sender, EventArgs e)
{
this.globalTerminalPresenter.Do2();
}
To allow our concrete TerminalPresenters to interoperate with form we will force our form to implement the following interface:
/// <summary>
/// This represents your UI in technology-independent manner
/// </summary>
public interface ITerminalView
{
String Title { get; set; }
String Input { get; set; }
String Output { get; set; }
String Button1_Text { get; set; }
String Button2_Text { get; set; }
IEnumerable<String> SelectionItems { get; set; }
void Clear();
}
public partial class MainForm : Form,
ITerminalView
{
...
#region ITerminalView implementation
public string Title
{
get { return this.Text; }
set { this.Text = value; }
}
public String Button1_Text
{
get { return this.button1.Text; }
set { this.button1.Text = value; }
}
public String Button2_Text
{
get { return this.button2.Text; }
set { this.button2.Text = value; }
}
public string Input
{
get { return this.textBox_Input.Text; }
set { this.textBox_Input.Text = value; }
}
public string Output
{
get { return this.textBox_Output.Text; }
set { this.textBox_Output.Text = value; }
}
public IEnumerable<string> SelectionItems
{
get { return this.comboBox.Items.Cast<String>(); }
set
{
this.comboBox.Items.Clear();
if (value == null)
return;
foreach (var item in value)
{
this.comboBox.Items.Add(item);
}
}
}
public void Clear()
{
this.comboBox.SelectedIndex = -1;
this.Title = String.Empty;
this.Input = String.Empty;
this.Output = String.Empty;
this.SelectionItems = null;
}
#endregion
For now we will create two TerminalPresenters - one to just allow selection of next option through combobox, one that calculates sum of two numbers. Both of them use the same base class.
/// <summary>
/// Base class for all presenters
/// </summary>
public abstract class TerminalPresenterBase : ITerminalPresenter
{
protected ITerminalView view;
public TerminalPresenterBase(ITerminalView view)
{
if (view == null)
throw new ArgumentNullException("view");
this.view = view;
this.Parent = this;
}
public abstract void UpdateUI();
public abstract ITerminalPresenter this[int index]
{
get;
}
public abstract ITerminalPresenter Do1();
public abstract ITerminalPresenter Do2();
public virtual ITerminalPresenter Parent
{
get;
set;
}
public virtual void Reset()
{
this.UpdateUI();
}
}
/// <summary>
/// Presenter whose sole goal is to allow user to select some other option and press next
/// </summary>
public class SelectOptionPresenter : TerminalPresenterBase
{
private IList<KeyValuePair<String, ITerminalPresenter>> options;
private ITerminalPresenter selected;
private String title;
public SelectOptionPresenter(ITerminalView view,
String title,
IList<KeyValuePair<String, ITerminalPresenter>> options)
: base(view)
{
if (options == null)
throw new ArgumentNullException("options");
this.title = title;
this.options = options;
foreach (var item in options)
{
item.Value.Parent = this;
}
}
public override void UpdateUI()
{
this.view.Clear();
this.view.Button1_Text = "Confirm selection";
this.view.Button2_Text = "Go back";
this.view.Title = title;
this.view.SelectionItems = options
.Select(opt => opt.Key);
}
public override ITerminalPresenter this[int index]
{
get
{
this.selected = this.options[index].Value;
return this;
}
}
public override ITerminalPresenter Do1()
{
return this.ConfirmSelection();
}
public override ITerminalPresenter Do2()
{
return this.GoBack();
}
public ITerminalPresenter ConfirmSelection()
{
this.selected.UpdateUI();
return this.selected;
}
public ITerminalPresenter GoBack()
{
this.Parent.UpdateUI();
return this.Parent;
}
}
public enum APlusBState
{
EnterA,
EnterB,
Result
}
public class StepActions
{
public Action UpdateUI { get; set; }
public Func<ITerminalPresenter> Do1 { get; set; }
public Func<ITerminalPresenter> Do2 { get; set; }
}
public class APlusBPresenter : TerminalPresenterBase
{
private Int32 a, b;
private APlusBState state;
private String error = null;
private Dictionary<APlusBState, StepActions> stateActions;
private void InitializeStateActions()
{
this.stateActions = new Dictionary<APlusBState, StepActions>();
this.stateActions.Add(APlusBState.EnterA,
new StepActions()
{
UpdateUI = () =>
{
this.view.Title = this.error ?? "Enter A";
this.view.Input = this.a.ToString();
this.view.Button1_Text = "Confirm A";
this.view.Button2_Text = "Exit";
},
Do1 = () => // Confirm A
{
if (!Int32.TryParse(this.view.Input, out this.a))
{
this.error = "A is in incorrect format. Enter A again";
return this;
}
this.error = null;
this.state = APlusBState.EnterB;
return this;
},
Do2 = () => // Exit
{
this.Reset();
return this.Parent;
}
});
this.stateActions.Add(APlusBState.EnterB,
new StepActions()
{
UpdateUI = () =>
{
this.view.Title = this.error ?? "Enter B";
this.view.Input = this.b.ToString();
this.view.Button1_Text = "Confirm B";
this.view.Button2_Text = "Back to A";
},
Do1 = () => // Confirm B
{
if (!Int32.TryParse(this.view.Input, out this.b))
{
this.error = "B is in incorrect format. Enter B again";
return this;
}
this.error = null;
this.state = APlusBState.Result;
return this;
},
Do2 = () => // Back to a
{
this.state = APlusBState.EnterA;
return this;
}
});
this.stateActions.Add(APlusBState.Result,
new StepActions()
{
UpdateUI = () =>
{
this.view.Title = String.Format("The result of {0} + {1}", this.a, this.b);
this.view.Output = (this.a + this.b).ToString();
this.view.Button1_Text = "Exit";
this.view.Button2_Text = "Back";
},
Do1 = () => // Exit
{
this.Reset();
return this.Parent;
},
Do2 = () => // Back to B
{
this.state = APlusBState.EnterB;
return this;
}
});
}
public APlusBPresenter(ITerminalView view) : base(view)
{
this.InitializeStateActions();
this.Reset();
}
public override void UpdateUI()
{
this.view.Clear();
this.stateActions[this.state].UpdateUI();
}
public override ITerminalPresenter this[int index]
{
get { throw new NotImplementedException(); }
}
public override ITerminalPresenter Do1()
{
var nextPresenter = this.stateActions[this.state].Do1();
nextPresenter.UpdateUI();
return nextPresenter;
}
public override ITerminalPresenter Do2()
{
var nextPresenter = this.stateActions[this.state].Do2();
nextPresenter.UpdateUI();
return nextPresenter;
}
public override void Reset()
{
this.state = APlusBState.EnterA;
this.a = 0;
this.b = 0;
this.error = null;
}
}
/// <summary>
/// Represents terminal presenter to use inside GUI. It handles current ISpecificTerminalPresenter inside itself.
/// </summary>
public class GlobalTerminalPresenter : IGlobalTerminalPresenter
{
#region Fields
private ITerminalPresenter current;
private Int32 selectedIndex;
#endregion
#region Constructors
public GlobalTerminalPresenter(ITerminalPresenter mainPresenter)
{
if (mainPresenter == null)
throw new ArgumentNullException("mainPresenter");
this.current = mainPresenter;
this.UpdateUI();
}
#endregion
public void UpdateUI()
{
this.current.UpdateUI();
}
public void Do1()
{
this.current = this.current.Do1();
}
public void Do2()
{
this.current = this.current.Do2();
}
public Int32 SelectedIndex
{
get
{
return this.selectedIndex;
}
set
{
this.selectedIndex = value;
if (value == -1)
return;
this.current = this.current[value];
}
}
public void Reset()
{
this.current.Reset();
}
}
Then we initialize them in the constructor of our form:
public partial class MainForm : Form,
ITerminalView
{
private IGlobalTerminalPresenter globalTerminalPresenter;
public MainForm()
{
InitializeComponent();
var nextLevelPresenters = new KeyValuePair<String, ITerminalPresenter>[]
{
new KeyValuePair<String, ITerminalPresenter>(
"A plus B",
new APlusBPresenter(this)),
new KeyValuePair<String, ITerminalPresenter>(
"Just empty selector",
new SelectOptionPresenter(this,
"Selector with no selection choices",
Enumerable
.Empty<KeyValuePair<String, ITerminalPresenter>>()
.ToArray()))
};
var topPresenter = new SelectOptionPresenter(this, "Select the option and press the confirm button", nextLevelPresenters);
this.globalTerminalPresenter = new GlobalTerminalPresenter(topPresenter);
}
P.S.1: These code snippets assume that you have form named MainForm that has two buttons - button1, button2, one combobox, two textBoxes - textBox_Input, textBox_Output.
P.S.2: The pattern used is close enough to Model-View-Presenter, just without DataBindings.
P.S.3 You can create more or less generic state machine Presenters if you modify APlusBPresenter code. Or try to shape up ChainXxxx... classes and interfaces.
P.S.4: And sorry for these walls of code. That's probably too much for [SO] format, so I've put ad-hoc proof of concept at GitHub - https://github.com/Podskal/StackOverflow_29870164.git. It is ugly in many aspects, but as it is, it can at least give few ideas about how to implement your own system.
P.S.5: There are a lot of problematic places in this code, so you should very carefully consider how you will build your own system from it.
Related
I have a list object that has several properties that I want to use for calculations for an model properties.
something like:
List<Cars>
that has properties for Wheels/Windows/HasRoof/FuelType, etc.
I have a model for "Parts" (class example below) that I want to fill but I have a few rules to apply, I'm going to pseudoCode what I think I should do, but I'm not sure if this is the approach for this:
public class Parts
{
public int AmountOfWheels { get; set; }
public int AmountOfWheelsForFuelTypeGas { get; set; }
public AmountOfWindowsForCarsWithRoof Type { get; set; }
}
public Parts Parts { get; set; }
this is what I want to fill:
foreach (var item in Cars)
{
Parts.AmountOfWheels =+ item.Wheels;
Parts.AmountOfWheelsForFuelTypeGas // <-- This is what I don't know
Parts.AmountOfWindowsForCarsWithRoof // <-- This is what I don't know
}
Then later I want to show the user this Parts object in a webApp, but I'm not sure how to populate this object.
The part I'm not sure if it's ok to do the calculations like this or shall I do something in the object model with properties
I think I know what you're getting at, but tell me if I miss the mark!
For most UIs including web apps, the UI box that is displaying something like AmountOfWheels is bound to changes of that property using the INotifyPropertyChanged interface. If your Parts class implements that interface, then when this line in your code executes:
Parts.AmountOfWheels =+ item.Wheels;
that will change the value of AmountOfWheels property and that will fire a property changed event. The calculation in turn will set the other properties (like AmountOfWheelsForFuelTypeGas). That fires its own changed event and the UI just picks up on that property change and shows the value.
// A class that notifies when its properties change
class Part : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// The private 'backing store' of the variable which is a Field
/// </summary>
private int _amountOfWheels = 1;
/// <summary>
/// The public property
/// </summary>
public int AmountOfWheels
{
get => _amountOfWheels;
set
{
if (_amountOfWheels != value)
{
_amountOfWheels = value;
OnPropertyChanged();
}
}
}
int _amountOfWheelsForFuelTypeGas = -1;
public int AmountOfWheelsForFuelTypeGas
{
get => _amountOfWheelsForFuelTypeGas;
set
{
if (_amountOfWheelsForFuelTypeGas != value)
{
_amountOfWheelsForFuelTypeGas = value;
OnPropertyChanged();
}
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
switch (propertyName)
{
case nameof(AmountOfWheels):
onAmountOfWheelsChanged();
break;
}
}
private void onAmountOfWheelsChanged()
{
// Perform some calculation based on AmountOfWheels.
AmountOfWheelsForFuelTypeGas = TestDataGenerator.Next(0,10);
}
private Random TestDataGenerator = new Random();
}
Test driver for it:
class Program
{
static void Main(string[] args)
{
var part = new Part();
part.PropertyChanged += (sender, e) =>
{
switch (e.PropertyName)
{
case nameof(Part.AmountOfWheelsForFuelTypeGas):
Console.WriteLine($"Amount of Wheels {part.AmountOfWheels} = (Rando test data){part.AmountOfWheelsForFuelTypeGas}");
break;
}
};
for (int i = 0; i < 5; i++)
{
part.AmountOfWheels = i;
}
}
}
I have an Item class in C# and I want to have two methods, OnRightClick and OnLeftClick, but I want every item to do different things upon using them (I am on Unity and this class is not a monoBehavior).
I heard about virtual methods, but from what I realized they can be overridden only from other classes that inherit them. However, I want to do without making a separate class for every item I make. How can I make those 2 methods vary?
I thought of using delegate, but it doesn't work the way I expected it to either. Is it even possible?
EDIT
(Ik the following line are not a thing but this is the best way I can somehow explain what I want to do)
Imagine having the following simple class Item
public class Item
{
private string name;
private float weight;
private int price;
private bool dropable;
public Item(string name, float weight, int price, bool dropable)
{
this.name = name;
this.weight = weight;
this.price = price;
this.dropable = dropable;
}
public Item(string name, float weight, int price)
{
this.name = name;
this.weight = weight;
this.price = price;
this.dropable = true;
}
public string GetName()
{
return this.name;
}
public float GetWeight()
{
return this.weight;
}
public int GetPrice()
{
return this.price;
}
public bool GetDropable()
{
return this.dropable;
}
public void SetDropable(bool dropable)
{
this.dropable = dropable;
}
}
I want to be able to make an OnRightClick and OnLeftClick that would vary from every item I create if I could do something like(As I said I know it's not valid but this is the best way I can explain it, also didn't mention it in the class above, this is the original class I currently have)
Item item1 = new Item(*all the stuff here*);
Item item2 = new Item(*other stuff*);
item1.OnRightClick = delegate {*what I want on right click*};
item1.OnRightClick(); //executes the function for item1
item2.OnRightClick = delegate {*other stuff on right click*};
item2.OnRightClick(); //executes a different function for item2
Again, this is not a valid code but I just used this to try and explain what I want to try and do, and to ask if there is any solutions to this that exist. In the worst case, if there aren't, I could just use virtual but I'd like that to be my last case of no choice.
You can have a parent Item class then different children item classes that inherit from it the syntax it
public class MyItem : MonoBehaviour {
public enum ItemType {HEALING, OFFENSIVE, CONSUMABLE, EQUIPMENT}
public ItemType itemType;
public float potency;
public MyItem(ItemType _it, float _potency) {
itemType = _it;
potency = _potency;
}
public void OnRightClick() {
switch (itemType) {
case ItemType.HEALING:
HealCharacter(character, potency);
break;
case ItemType.OFFENSIVE:
break;
// more cases
}
}
public void OnLeftClick() {
//fill in like onrightclick
}
}
You can use Action and Func to do what you are asking.
https://learn.microsoft.com/en-us/dotnet/api/system.action-1?view=netcore-3.1
https://learn.microsoft.com/en-us/dotnet/api/system.func-2?view=netcore-3.1
public enum ItemType
{
Sword,
Shield,
Potion
}
public class Item
{
private readonly Action leftClickHandler;
private readonly Action<int> leftClickHandlerWithParam;
private readonly Action rightClickHandler;
private readonly Action<int> rightClickHandlerWithParam;
public Item(ItemType itemType)
{
switch (itemType)
{
case ItemType.Potion:
this.rightClickHandler = this.HandleClickWithFlash;
this.leftClickHandler = this.HandleClickWithSound;
this.leftClickHandlerWithParam = this.HandleClickWithFlash;
this.rightClickHandlerWithParam = this.HandleClickWithSound;
break;
}
}
public void HandleLeftClick()
{
this.leftClickHandler();
}
public void HandleRightClick()
{
this.rightClickHandler();
}
private void HandleClickWithFlash()
{
// Logic here.
}
private void HandleClickWithFlash(int parameter)
{
// Logic here.
}
private void HandleClickWithSound()
{
// Logic here.
}
private void HandleClickWithSound(int parameter)
{
// Logic here.
}
}
Here it is with the items exposed, if say you wanted a item factory concept.
public class ItemSettableHandlers
{
public ItemSettableHandlers()
{
}
public Action LeftClickHandler { get; set; }
public Action RightClickHandler { get; set; }
public void HandleLeftClick()
{
this.LeftClickHandler?.Invoke();
}
public void HandleRightClick()
{
this.RightClickHandler?.Invoke();
}
}
public class ItemCreator
{
public void CreateItems()
{
var itemSword = new ItemSettableHandlers();
itemSword.LeftClickHandler = () =>
{
// Do sword left click here.
};
itemSword.RightClickHandler = () =>
{
// Do sword right click here.
};
var itemShield = new ItemSettableHandlers();
itemShield.LeftClickHandler = () =>
{
// Do shield left click here.
};
itemShield.RightClickHandler = () =>
{
// Do shield right click here.
};
}
}
You could use EventHandler:
in you class, you define:
public event EventHandler RightClick;
public void OnRightClick()
{
EventHandler handler = RightClick;
if (null != handler) handler(this, EventArgs.Empty);
}
You use like that:
// Enable Event
RightClick += new EventHandler(OnRightClick);
OnRightClick();
public void OnRightClick(object s, EventArgs e)
{
}
I have created a custom menu item which appears in the default menu which pops up when selecting text on my custom WebView.
On clicking on the menu item it calls EvaluateJavascript to get the selected WebView text, and then passes the text to another page.
However after performing this action once or twice, some text on certain areas of the screen start to become unresponsive to clicks eg. text on the parts of the WebView become unselectable, clicks on that part of the screen on other pages becomes unresponsive and even the soft keyboard becomes unclickable in some spots. If this continues for a while sometimes my app will then suddenly freeze the entire operating system and I have to soft reset my phone. It appears that there maybe some serious memory leakage going on.
I create my custom menu item in the MainActivity class:
public override void OnActionModeStarted(ActionMode mode)
{
if (Root.IsCurrentPageType<DictPage>() && DictP.IsWebViewFocused())
{
IMenu menu = mode.Menu;
menu.Add("To Notes");
menu.GetItem(0).SetOnMenuItemClickListener(new MyMenuItemOnMenuItemClickListener(this, mode));
}
base.OnActionModeStarted(mode);
}
It is then handled in the Listener class...
public class MyMenuItemOnMenuItemClickListener : Java.Lang.Object, IMenuItemOnMenuItemClickListener
{
private MainActivity mContext;
ActionMode _mode;
public MyMenuItemOnMenuItemClickListener(MainActivity activity, ActionMode mode)
{
this.mContext = activity;
_mode = mode;
}
public bool OnMenuItemClick(IMenuItem item)
{
WEB.CopyToMainNotes();
Device.BeginInvokeOnMainThread(() =>
{
//close menu if clicked
_mode?.Finish();
});
return true;
}
}
...which calls CopyToMainNotes on my derived WebView class and its associated Renderer and EventHandler classes:
public class WebViewEx : Xamarin.Forms.WebView
{
public static WebViewEx WEB;
//Namespace
//YourClass
public event WebViewExEventHandler CallNativeMethodEvent;
public void CallNativeMethod(WebViewExEventType type)
{
WebViewExEventArgs e = new WebViewExEventArgs();
e.EventType = type;
CallNativeMethodEvent?.Invoke(this, e);
}
public WebViewEx()
{
WEB = this;
}
public void CopyToMainNotes()
{
Device.BeginInvokeOnMainThread(() =>
{
CallNativeMethod(WebViewExEventType.copyToMainNotes);
});
}
}
public delegate void WebViewExEventHandler(object sender, WebViewExEventArgs e);
public class WebViewExEventArgs : EventArgs
{
public enum WebViewExEventType { copyToMainNotes };
public WebViewExEventType EventType = WebViewExEventType.copyToMainNotes;
public WebViewExEventArgs() : base()
{
}
}
public class WebViewExRenderer : WebViewRenderer
{
public WebViewExRenderer(Android.Content.Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
WebViewEx ex = e.NewElement as WebViewEx;
ex.CallNativeMethodEvent += WebViewEx_CallNativeMethodEvent;
}
}
internal class JavascriptCallback : Java.Lang.Object, IValueCallback
{
public JavascriptCallback(Action<string> callback)
{
_callback = callback;
}
private Action<string> _callback;
public void OnReceiveValue(Java.Lang.Object value)
{
_callback?.Invoke(Convert.ToString(value));
}
}
private void WebViewEx_CallNativeMethodEvent(object sender, WebViewExEventArgs e)
{
switch (e.EventType)
{
case WebViewExEventType.copyToMainNotes:
{
CopyToMainNotes();
break;
}
}
}
public void CopyToMainNotes()
{
string script = "(function(){ return window.getSelection().toString()})()";
var response = string.Empty;
Control?.EvaluateJavascript(script, new JavascriptCallback((r) =>
{
response = r;
Device.BeginInvokeOnMainThread(() =>
{
DPage.CopyThisTextToAnotherPage(response.ToString().Trim('\"'));
});
}));
}
}
The CopyToMainNotes method above is where the EvaluateJavascript takes place and the selected text finally gets sent to another page.
Any ideas where I might be going wrong here? Thanks in advance!
I have been trying this for a few days now. I am new to coding and quite new to WPF and DataGrids. Any help is greatly appreciated.
See Image of the datagrid
Basically the datagrid on the left has a different ItemSource to the one on the right.
In the right datagrid, the user selects the brand for which he wants to calculate the total amount. I capture the selected row and get the brand rate as follows -
private void BrandGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
List<DataGridCellInfo> info = BrandGrid.SelectedCells.ToList();
rate = 0;
if (info.Count > 0)
{
Brand i = (Brand)info[1].Item;
rate = i.Rate;
}
}
Now how do I tell the other class to use the selected rate to calculate the total amount. In the same fashion as I calculate the total area
TotalArea = length * quantity * someConstantWidth (this one was easy using INotifyPropertyChanged). When the user edited the length and quantity in the datagrid, the TotalArea would update simultaneously.
Whereas here TotalAmount = TotalArea * SelectedRate has gotten me quite confused as to how to send the information from the MainWindow.BrandGrid_SelectedCellsChanged to another class.
I have tried poking around with delegate and events unsuccessfully cause in my example application I'm finding it difficult on how to implement it.
What is the best method? How do I go on about this?
UPDATE
Thanks you so much for such a detailed response! I have few more doubts regarding the implementation ,
(1) In step 1 the line of code: public void SelectBrand() => OnBrandSelect?.Invoke(this, Rate);
I assume here Rate is the variable name and not the type? My variable Rate is a double type.
(2) To address your note on the Result Class: The length and quantity are user inputs - using which Total Area is calculated. Here Length, Quantity, Area etc are within the same class Result and I use INotifyPropertyChanged to update the TotalArea column.
(3) In your example for Result Class, it requires me to create the ResultObject with a Brand Type input. Any other way? As user should be able to include all the length and quantities of an order placed by a customer and then select the brand later.
I would like the output to be similar to how INotifyPropertyChanged handles changes. It changes the cells real time as I am changing the inputs in the other cells - similar behavior if possible would be awesome.
UPDATE 2
B.Spangenberg, you have given an excellent solution. I tried your code and added a button to add items and the code works perfectly but I guess I had some requirements missing from my question.
The DataGrid I have is "IsEnabled" and allows a new row to be added - once the present row is added, the new row automatically appears which can be edited.
By understanding your solution, here are the proper requirements -
(1) No button to add items to the OrderGrid. New row appears automatically.
(2) The brand can be selected first before entering the items or after entering the items.
(3) Once the user selects a brand ALL the items' TotalAmount are updated. There is NO selection of an item in the OrderGrid.
(4) If the user adds a new item in the OrderGrid after selecting a brand, the new item's TotalAmount is calculated based on the brand that is already presently selected.
Note: You are right about "SelectedCellsChanged" I have changed that to a MouseDoubleClick event. It's now very reliable and also a better feel for the user. THANK YOU!
So, I assume you're trying to invoke an event from your selected brand to convey to anyone listing that the brand rate is (), which is then used to calculate the Total.
Step 1:
You'll need to add an event to your "Brand" class and create a public method to invoke it internally.
class Brand
{
public string Name { get; set; }
public Rate Rate { get; set; }
public void SelectBrand() => OnBrandSelect?.Invoke(this, Rate);
public event EventHandler<Rate> OnBrandSelect;
}
Step 2:
Hook your brand events when you load them into the collection that contains them which is used as the grid source. Basic example below.
// load brands from db or elswhere.
List<Brand> brandsSelectedToLoad = new List<Brand>()
ObservableCollection<Brand> BrandDisplayCollectionToBeUsedAsGridSource = new ObservableCollection<Brand>();
foreach (Brand brand in brandsSelectedToLoad)
{
brand.OnBrandSelect += (s, args) => { /* Call calculation method (TotalAmount = TotalArea * args -- args is rate) and set the result */}
BrandDisplayCollectionToBeUsedAsGridSource.Add(brand);
}
Step 3:
Then, when you select your brand from the grid, you invoke the brand event. Any listeners should then do their jobs.
private void BrandGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
List<DataGridCellInfo> info = BrandGrid.SelectedCells.ToList();
if (info.Count > 0)
{
Brand selectedBrand = info[1].Item as Brand;
if (selectedBrand == null)
return;
selectedBrand.SelectBrand();
}
}
NOTE :
The other grid which I'll refer to as the "Result Grid" will most likely need to reflect these calculations based on rate invoked by the event, within the Total Amount column.
I assume these items in your "Result Grid" are actual result objects,
but I don't see any clear relation between the result record and the
selected brand. (unless it's hidden within the result object). Which
leaves me to believe that you just add the record to the Result Grid on brand select and forget it.
You will need to embed the same Brand object reference that you load into you Brands Grid into your result object and hook it's event inside of the result object. (Almost the same as in step step two). You can ignore step two then and use the below.
Example :
class Result : INotifyPropertyChanged
{
private double total;
public Result(Brand brandRef)
{
this.Brand = brandRef;
brand.OnBrandSelect += (s, args) =>
{
/* Call calculation method (TotalAmount = TotalArea *
args -- args is rate) and set the result */
Total = /*(TotalAmount = TotalArea *
args -- args is rate)*/;
}
}
public double Total
{
get { return total; }
set
{
total = value;
NotifyPropertyChanged();
}
}
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Update Response
The rate object can be replace by your double type. Just swap all "Rate" with double.
I see. So the brand selection is only available after the user has configured the length and quantity inputs. Therefore having the brand parameter within the construct is not possible, cause you need your result object instantiated beforehand.
Do the following :
1) Create brand object as I have it in step one. Just replace the Rate with double.
2) Add a method called "Calculate(double rate)" to your result object which takes double. Within this method you run you calculation for total and set the total property.
public void Caculate(double rate)
{
TotalAmount = TotalArea * rate;
}
3) So now you need some way to keep you event subscribed to the selected grid row on the result grid. So each time you add a result object into your grid source collection, you will need to hook each brand object event to it.
So I assume somewhere you have a button that adds a new record to the result grid to be configured by the user and then he selects the brand. Just before the point where you add the object to the result object collect. Hook it with the following.
ResultObject resultObj = new ResultObject()
BrandCollection.All(brand =>
{
brand.OnBrandSelect += (s, rate) =>
{
resultObj.Calculate(rate);
}; return true;
});
resultGridCollection.Add(resultObj);
So now each brand object has a hooked function that calculates and sets the existing result object on select. You still use Step 3 to invoke the selection event.
Keep in mind you will need to unsubscribe all brand events every time you add a new object, otherwise the brand selection will keep altering all your results. If you want to support changes on selected GridResult instead, you just need to move the subscription code to from the add method to the selected grid cell method for result.
I hope this all makes sense.
Update 2
I decided to go the extra mile for you. Here is the code you need. All of it.
namespace GridSelection
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
MainWindowViewModel model = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
OrdersGrid.SelectedCellsChanged += OrdersGrid_SelectedCellsChanged;
BrandGrid.SelectedCellsChanged += BrandGrid_SelectedCellsChanged;
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
model.Load();
this.DataContext = model;
}
private void OrdersGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
DataGrid grid = sender as DataGrid;
Order selectedOrder = grid.SelectedItem as Order;
if (selectedOrder == null)
return;
model.BrandCollection.All(b =>
{
b.UnsubscribeAll();
b.OnBrandSelect += (s, rate) =>
{
selectedOrder.CalculateTotal(rate);
}; return true;
});
}
private void BrandGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
DataGrid grid = sender as DataGrid;
Brand selectedbrand = grid.SelectedItem as Brand;
if (selectedbrand == null)
return;
selectedbrand.InvokeBrandSelected();
}
}
internal class MainWindowViewModel
{
public ObservableCollection<Brand> BrandCollection { get; private set; }
public ObservableCollection<Order> OrderCollection { get; private set; }
public ICommand AddNewOrderCommand { get; private set; }
public void Load()
{
BrandCollection = new ObservableCollection<Brand>
{
new Brand() { Name = "Brand One", Rate = 20 },
new Brand() { Name = "Brand Two", Rate = 30 },
new Brand() { Name = "Brand Three", Rate = 50 }
};
OrderCollection = new ObservableCollection<Order>();
AddNewOrderCommand = new CustomCommand(p =>
{
OrderCollection.Add(new Order());
});
}
}
public class Order : INotifyPropertyChanged
{
#region Private Variables
private int length;
private int quantity;
private int totalArea;
private double totalAmount;
#endregion
#region Public Properties
public int Length
{
get { return length; }
set
{
length = value;
NotifyPropertyChanged();
CalculateArea();
}
}
public int Quantity
{
get { return quantity; }
set
{
quantity = value;
NotifyPropertyChanged();
CalculateArea();
}
}
public int TotalArea
{
get { return totalArea; }
set
{
totalArea = value;
NotifyPropertyChanged();
}
}
public double TotalAmount
{
get { return totalAmount; }
set
{
totalAmount = value;
NotifyPropertyChanged();
}
}
#endregion
private void CalculateArea()
{
TotalArea = this.Length * this.Quantity;
}
public void CalculateTotal(double rate)
{
TotalAmount = this.TotalArea * rate;
}
#region Public Methods
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class Brand
{
public string Name { get; set; }
public double Rate { get; set; }
public void InvokeBrandSelected() => OnBrandSelect?.Invoke(this, Rate);
public void UnsubscribeAll() => OnBrandSelect = null;
public event EventHandler<double> OnBrandSelect;
}
// Interface
public interface ICustomCommand : ICommand
{
event EventHandler<object> Executed;
}
// Command Class
public class CustomCommand : ICustomCommand
{
#region Private Fields
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
#endregion
#region Constructor
public CustomCommand(Action<object> execute) : this(execute, null)
{
}
public CustomCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute ?? (x => true);
}
#endregion
#region Public Methods
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter = null)
{
Refresh();
_execute(parameter);
Executed?.Invoke(this, parameter);
Refresh();
}
public void Refresh()
{
CommandManager.InvalidateRequerySuggested();
}
#endregion
#region Events
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public event EventHandler<object> Executed;
#endregion
}
}
Just to note, I noticed that the on grid select is not very reliable. You will just need to look into that.It seems when the grid gains focus and is first selected, that does not fire the SelectedCellsChanged.
UPDATE
Here is the code based on the update you posted.
namespace GridSelection
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
MainWindowViewModel model = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
BrandGrid.MouseDoubleClick += BrandGrid_MouseDown;
OrdersGrid.InitializingNewItem += OrdersGrid_InitializingNewItem; ;
this.Loaded += MainWindow_Loaded;
}
private void OrdersGrid_InitializingNewItem(object sender, InitializingNewItemEventArgs e)
{
Order newOrder = e.NewItem as Order;
if (newOrder == null)
return;
if(model.CurrentBrand != null)
{
newOrder.UpdateRate(model.CurrentBrand.Rate);
}
model.BrandCollection.All(b =>
{
b.OnBrandSelect += (s, rate) =>
{
newOrder.UpdateRate(model.CurrentBrand.Rate);
newOrder.CalculateTotal();
}; return true;
});
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
model.Load();
this.DataContext = model;
}
private void BrandGrid_MouseDown(object sender, MouseButtonEventArgs e)
{
DataGrid grid = sender as DataGrid;
Brand selectedbrand = grid.SelectedItem as Brand;
if (selectedbrand == null)
return;
selectedbrand.InvokeBrandSelected();
}
}
internal class MainWindowViewModel : INotifyPropertyChanged
{
private Brand currentBrand;
public ObservableCollection<Brand> BrandCollection { get; private set; }
public ObservableCollection<Order> OrderCollection { get; private set; }
public Brand CurrentBrand
{
get { return currentBrand; }
set
{
currentBrand = value;
NotifyPropertyChanged();
}
}
public void Load()
{
BrandCollection = new ObservableCollection<Brand>
{
new Brand() { Name = "Brand One", Rate = 20 },
new Brand() { Name = "Brand Two", Rate = 30 },
new Brand() { Name = "Brand Three", Rate = 50 }
};
OrderCollection = new ObservableCollection<Order>();
}
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Order : INotifyPropertyChanged
{
#region Private Variables
private int length;
private int quantity;
private int totalArea;
private double totalAmount;
#endregion
public Order()
{
}
#region Properties
private double Rate { get; private set; }
public int Length
{
get { return length; }
set
{
length = value;
NotifyPropertyChanged();
CalculateArea();
CalculateTotal();
}
}
public int Quantity
{
get { return quantity; }
set
{
quantity = value;
NotifyPropertyChanged();
CalculateArea();
CalculateTotal();
}
}
public int TotalArea
{
get { return totalArea; }
set
{
totalArea = value;
NotifyPropertyChanged();
}
}
public double TotalAmount
{
get { return totalAmount; }
set
{
totalAmount = value;
NotifyPropertyChanged();
}
}
#endregion
#region Methods
private void CalculateArea()
{
TotalArea = this.Length * this.Quantity;
}
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void CalculateTotal()
{
TotalAmount = this.TotalArea * this.Rate;
}
public void UpdateRate(double rate)
{
Rate = rate;
}
#endregion
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class Brand
{
public string Name { get; set; }
public double Rate { get; set; }
public void InvokeBrandSelected() => OnBrandSelect?.Invoke(this, Rate);
public void UnsubscribeAll() => OnBrandSelect = null;
public event EventHandler<double> OnBrandSelect;
}
}
Writing this for anyone who might expect a different behavior with INotifyPropertyChanged.
I noticed that real-time value change cannot be triggered with -
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
By real-time I mean - as you are changing a value in datagrid, another value in a different column, same row changes simultaneously. Whereas in the above code, you need to click on the cell for the value to update.
The best way for this neat behavior is -
public void NotifyPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
And call this method as NotifyPropertyChanged(yourVariableName);
The problem is as follows:
In my User Control i have Label List (List ) which fills adding Labels which UC through a "Smart Tag" (using a DesignerActionMethodItem).
The problem is that it works correctly when we are in design time, for example, I add 3 items at design time, but when I test the application these items disappear as if they had never added.
P.S.:
I have:
MyControl class,
[Designer(typeof(MenuItemPanelDesigner ))]
public partial class MenuItemPanel : UserControl
{
private List<Label> _listaItems;
public MenuItemPanel()
{
InitializeComponent();
}
public List<Label> ListaItems
{
get
{
if (this._listaItems == null)
{
this._listaItems = new List<Label>();
}
return this._listaItems;
}
}
public void AgregarItem()
{
Label nuevoItem = new Label();
nuevoItem.Text = "Item " + this._listaItems.Count;
nuevoItem.AutoSize = false;
nuevoItem.TextAlign = ContentAlignment.MiddleCenter;
nuevoItem.Cursor = Cursors.Hand;
this.ListaItems.Add(nuevoItem);
this.Controls.Add(nuevoItem);
nuevoItem.Dock = DockStyle.Top;
nuevoItem.Height = 50;
}
}
MyControlDesigner class
class MenuItemPanelDesigner : System.Windows.Forms.Design.ControlDesigner
{
private DesignerActionListCollection actionLists;
public override DesignerActionListCollection ActionLists
{
get
{
if (null == actionLists)
{
actionLists = new DesignerActionListCollection();
actionLists.Add(new MenuItemPanelDesignerActionList(this.Component));
}
return actionLists;
}
}
}
and MyControlDesignerActionList
class MenuItemPanelDesignerActionList : DesignerActionList
{
private MenuItemPanel colUserControl;
private DesignerActionUIService designerActionUISvc = null;
//The constructor associates the control with the smart tag list.
public MenuItemPanelDesignerActionList(IComponent component) : base(component)
{
this.colUserControl = (MenuItemPanel)component;
this.designerActionUISvc = (DesignerActionUIService)GetService(typeof(DesignerActionUIService));
}
// Implementation of this abstract method creates smart tag items,
// associates their targets, and collects into list.
public override DesignerActionItemCollection GetSortedActionItems()
{
DesignerActionItemCollection items = new DesignerActionItemCollection();
//Define static section header entries.
items.Add(new DesignerActionHeaderItem("Items"));
items.Add(new DesignerActionMethodItem(this,"AgregarItem","Agregar Item"));
return items;
}
// Metodos
public void AgregarItem()
{
this.colUserControl.AgregarItem();
}
}