I want to eliminate all of these case statements (too messy) - c#

I am writing a test program which returns a string "P3-PASS" or "P3-FAIL. In all there are 12 possible tests, P3 to P14 ("P3-FAIL" to "P14-PASS").
I have a button "All_Tests" which calls each test 1 by 1, and the associated button changes colour based on the result.
Ideally I want to do something like PageNum.Background = Brushes.Red, but I can't do this because I can't use a string to access the button. Hence the case statements below. Is there a way to simplify this, because it looks awful. OR is there a way to access the relevant button using a string, or similar. Many Thanks
int PageNum = Int32.Parse(PageTstName);
switch (PageNum)
{
case 3:
if (TstResult == "PASS")
{
Pg3.Background = Brushes.SeaGreen;
Pg3.Foreground = Brushes.White;
}
else // TstResult is "FAIL"
{
Pg3.Background = Brushes.Red;
Pg3.Foreground = Brushes.White;
}
break;
case 4:
if (TstResult == "PASS")
{
Pg4.Background = Brushes.SeaGreen;
Pg4.Foreground = Brushes.White;
}
else // TstResult is "FAIL"
{
Pg4.Background = Brushes.Red;
Pg4.Foreground = Brushes.White;
}
break;
case 5: .....etc

You can create a dictionary that maps the numbers to the controls, e.g.
var controlsByPageNum = new Dictionary<int, Button>()
{
{ 3, Pg3 },
{ 4, Pg4 },
{ 5, Pg5 },
// ...
}
When receiving the test results, you can get the control like this:
int PageNum = Int32.Parse(PageTstName);
var btn = controlsByPageNum[PageNum];
if (TstResult == "PASS")
{
btn.Background = Brushes.SeaGreen;
btn.Foreground = Brushes.White;
}
else // TstResult is "FAIL"
{
btn.Background = Brushes.Red;
btn.Foreground = Brushes.White;
}

Create a method to apply your colors:
private static void ApplyColors(Control control, Brush background, Brush foreground)
{
control.Background = background;
control.Foreground = foreground;
}
And a dictionary to map each case:
private readonly Dictionary<string, Action> dict =
new Dictionary<string, Action>();
Now, in your constructor, fill the dictionary:
dict[$"3~PASS"] = () => ApplyColors(Pg3, Brushes.SeaGreen, Brushes.White);
dict[$"3~FAIL"] = () => ApplyColors(Pg3, Brushes.Red, Brushes.White);
dict[$"4~PASS"] = () => ApplyColors(Pg4, Brushes.SeaGreen, Brushes.White);
dict[$"4~FAIL"] = () => ApplyColors(Pg4, Brushes.Red, Brushes.White);
Here, for each page and result, you define the action to execute. With this, now you can manage your current switch in this way:
var key = $"{PageTstName}~{TstResult}";
if (dict.TryGetValue(key, out Action action))
{
action();
}
else
{
// A non mapped case...
}

Related

How to Use a Boolean to Compare Action<int>

I have a method as below
public void RequestTPCost(Action<int> callback, Action<int> enemyCallback)
{
if (OnTPSelectionNeeded == null)
{
callback?.Invoke(0);
enemyCallback?.Invoke(0);
return;
}
if (TurnSystem.Instance.IsPlayerTurn())
{
//Player turn use shooting unit is selected and target is enemy
OnTPSelectionNeeded?.Invoke(this, new TPSelectionInfo()
{
callback = callback,
enemyCallback = enemyCallback,
TPAvailable = selectedUnit.GetTacticalPoints(),
enemyTPAvailable = targetUnit.GetTacticalPoints(),
});
}
else
{
OnTPSelectionNeeded?.Invoke(this, new TPSelectionInfo()
{
callback = callback,
enemyCallback = enemyCallback,
TPAvailable = targetUnit.GetTacticalPoints(),
enemyTPAvailable = selectedUnit.GetTacticalPoints()
});
}
}
and I use it elsewhere like this
private void UnitActionSystem_OnTPSelectionNeeded(object sender, UnitActionSystem.TPSelectionInfo e)
{
maxTP = e.TPAvailable;
maxEnemyTP = e.enemyTPAvailable;
callback = e.callback;
enemyCallback = e.enemyCallback;
TPAmount = 1;
enemyTPAmount = 0;
}
public void TPConfirm()
{
callback?.Invoke(TPAmount);
UnitActionSystem.Instance.GetSelectedUnit().
SpendTacticalPoints(TPAmount);
enemyTPAmount = Random.Range(0, (maxEnemyTP + 1));
enemyCallback?.Invoke(enemyTPAmount);
UnitActionSystem.Instance.GetTargetUnit().
SpendTacticalPoints(enemyTPAmount);
}
private void NextState()
{
switch (state)
{
case State.Aiming:
state = State.Choosing;
stateTimer = 0.1f;
UnitActionSystem.Instance.RequestTPCost(ConfirmTPCost,
ConfirmEnemyTPCost);
break;
case State.Choosing:
state = State.Shooting;
float shootingStateTime = 0.1f; //Not a frame
stateTimer = shootingStateTime;
break;
case State.Shooting:
state = State.Cooloff;
float coolOffStateTime = 0.5f;
stateTimer = coolOffStateTime;
Debug.Log("Shooting");
break;
case State.Cooloff:
ActionComplete();
break;
}
}
private void ConfirmTPCost(int value)
{
Debug.Log($"TP = {value}");
NextState();
}
private void ConfirmEnemyTPCost(int value)
{
Debug.Log($"EnemyTP = {value}");
//NextState();
}
Now I want to check if ConfirmTPCost < ConfirmEnemyTPCost but am not sure how to simply do that.
I have a roundabout way using events and more UnityActionSystem functions to call the instance and work through that but it seems very clunky and prone to breaking. Is there a better way to get these values and check which is more?
var cost = 0;
var enemyCost = 0;
UnitActionSystem.Instance.RequestTPCost(i => cost = i, i => enemyCost = i);
Debug.Log($"TP = {cost}");
Debug.Log($"EnemyTP = {enemyCost}");
if (cost < enemyCost)
{
//Do your stuff here
}
As said it sounds like there is actually some asynchronous parts involved.
So you either will want to properly implement async and await patterns and wait for both values to be available.
Or alternatively you could somewhat fake that behavior using e.g. nullables and a local function ike
int? cost = null;
int? enemyCost = null;
UnitActionSystem.Instance.RequestTPCost(i =>
{
cost = i;
if(enemyCost != null)
{
Validate();
}
}, i =>
{
enemyCost = i;
if(cost != null)
{
Validate();
}
});
// Can be a local function
// otherwise pass in the two int values via parameters
void Validate()
{
Debug.Log($"TP = {cost.Value}");
Debug.Log($"EnemyTP = {enemyCost.Value}");
if (cost.Value < enemyCost.Value)
{
//Do your stuff here
}
}
Idea here: No matter which of both callbacks fires first, it is ignored and only for the second one both values are already available and the actual handler method can be invoked

Get column index as constant value

Currently i am having code like this inside dataGridView1_CellValidating event:
if(e.ColumnIndex == dataGridView1.Columns["FIRST"].Index)
{
// Some code
}
else if(e.ColumnIndex == dataGridView1.Columns["Second"].Index)
{
// Some code
}
else if(e.ColumnIndex == dataGridView1.Columns["Third"].Index)
{
// Some code
}
And it is like this because i cannot use it in switch statement like:
switch(e.ColumnIndex)
{
case dataGridView.Columns["First"].Index:
break;
case dataGridView.Columns["Second"].Index:
break;
case dataGridView.Columns["Third"].Index:
break;
}
returns me error on case line Expecting constant value.
So how can i make this work?
The switch statement is complaining because the “case” portion of the statement “requires” a “CONSTANT” value. The statement dataGridView.Columns["First"].Index will always return the same value… unless you move the column… which you can do. This is why the compiler is going to look at the retuned value from dataGridView.Columns["First"].Index as NOT a “constant”.
This makes sense in the fact that the “column index” for the column named “First” could be at ANY column index in the grid… Hence the error.
A possible solution is to grab the current columns “Name” string value then switch off the column “Name” like below.
string columnName = dataGridView.Columns[e.ColumnIndex].Name;
switch (columnName) {
case "First":
MessageBox.Show("Cell Validated is in 'FIRST' column");
break;
case "Second":
MessageBox.Show("Cell Validated is in 'Second' column");
break;
case "Third":
MessageBox.Show("Cell Validated is in 'Third' column");
break;
}
If you really want to use switch you can make use of pattern matching in switch case
PS: For C# 7.0 or above
switch(e.ColumnIndex)
{
case var _ when (dataGridView.Columns["First"].Index == e.ColumnIndex):
break;
case var _ when (dataGridView.Columns["Second"].Index == e.ColumnIndex):
break;
case var _ when (dataGridView.Columns["Third"].Index == e.ColumnIndex):
break;
}
Maybe, you first make constant values and assign dataGridView.Columns["First"].Index to it.
For example:
int a = {given index}
const int IndexOfFirstCol = dataGridView.Columns["First"].Index;
const int IndexOfSecCol = dataGridView.Columns["Second"].Index;
then,
switch(a)
{
case IndexOfFirstCol:
//do smth
break;
case IndexOfSecCol:
//do smth
break;
}
If you cannot use pattern matching from C# 7.0, there is also another way by using dictonaries where your keys are functions checking the conditions (cases) and the values are the actions you want to perform. For your code it would look like:
private void dataGridView1_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
var caseDictionary = new Dictionary<Func<bool>, Action>()
{
{ () => (e.ColumnIndex == dataGridView1.Columns["First"].Index), () => { MessageBox.Show("First");}},
{ () => (e.ColumnIndex == dataGridView1.Columns["Second"].Index), () => { MessageBox.Show("Second");}},
{ () => (e.ColumnIndex == dataGridView1.Columns["Third"].Index), () => { MessageBox.Show("Third");}}
};
caseDictionary.Where(caseRecord => caseRecord.Key()).Select(action => action.Value).FirstOrDefault()?.Invoke();
}
You could of course declare the Dictionary in your constructor and just call it in the CellValidating event.
I would have another approach, using a Dictionnary (from the namespace System.Collections.Generic) of methods built in such way
The key is the index of the column in the datagridview ("First", "Second" ...)
The value is a Delegate to the method to do (what replaces your // some code in each if/else if
In example :
/*
* This example is written for console application, that can be tested easily.
* The logic can be rewritten for WinForm
*/
static void TheFirstCase()
{
//This should be replaced by the differents actions you want to do
Console.WriteLine("first case");
}
static void TheSecondtCase()
{
Console.WriteLine("second case");
}
static void TheThirdCase()
{
Console.WriteLine("third case");
}
static void Main(string[] args)
{
Dictionary<string, Delegate> MyDic = new Dictionary<string, Delegate>
{
//If you need parameters in the TheFirstCase(), use new Action<TypeOfTheFirstParam, TypeOfTheSecondParam, ...>(TheFirstCase)
//If your Method needs to return something, use Func instead of Action
{ "First", new Action(TheFirstCase) },
{ "Second", new Action(TheSecondtCase) },
{ "Third", new Action(TheThirdCase) }
};
// in your question, this is e.ColumnIndex
var ValueInColIndex = 42;
// in your question, this is dataGridView.Columns
var DataGridViewDatas = new Dictionary<string, int>
{
{ "First", 0 },
{ "Second", 42 },
{ "Third", 69 }
};
foreach (var MyAction in MyDic)
{
if (DataGridViewDatas[MyAction.Key] == ValueInColIndex)
{
MyAction.Value.DynamicInvoke();
}
}
}
Outputs :
second case

Using ParallelFor to change listview

I have a function which modifies my listview "inputList",the listview has 5 columns. The function is supposed to check the cells in the listview,the cells in the last column will have "NORMAL" with green background if there is nothing wrong and if there is something wrong, it will have "ERROR" with red background in the cell in the last column and also change the color of the cells that are wrong in the same line from the other columns.
There are 4k items, I know that it's pointless to have so many item since the user is not going to read them but I was asked to this way.
I'm using a function with backgroundworker but its not fast enough and thought parallelFor would be faster. But when I try the parallelFor, its freezing the program.
This is the function with backgroundworker which is working but its too slow:
private void bw3_DoWork(object sender, DoWorkEventArgs e)
{
String tempTrain = "";
String tempPredic = "";
String tempNet = "";
Boolean bTrain;
Boolean bPredic;
Boolean bNetErro;
int n;
int nNet = 0;
String tTrain = "";
String tPredic = "";
int nList = 0;
this.Invoke(new MethodInvoker(() => {
nList = inputList.Items.Count;
nNet = menuNetwork.Items.Count;
tTrain = dtrainTextBox.Text;
tPredic = dpredicTextBox.Text;
}));
for (int i = 0; i < nList; i++)
{
ListViewItem.ListViewSubItem temp1 = new ListViewItem.ListViewSubItem();
temp1.BackColor = Color.LightGreen;
temp1.Text = "NORMAL";
this.Invoke(new MethodInvoker(() =>
{
inputList.Items[i].SubItems[0].BackColor = Color.White;
inputList.Items[i].SubItems[1].BackColor = Color.White;
inputList.Items[i].SubItems[2].BackColor = Color.White;
tempTrain = String.Format("{0}\\{1}", tTrain, inputList.Items[i].SubItems[1].Text);
tempPredic = String.Format("{0}\\{1}", tPredic, inputList.Items[i].SubItems[2].Text);
tempNet = (String)inputList.Items[i].SubItems[0].Tag;
}));
bTrain = (!File.Exists(tempTrain));
bPredic = (!File.Exists(tempPredic));
bNetErro = !(int.TryParse(tempNet, out n));
if (!bNetErro)
{
if (!(n < nNet))
{
bNetErro = true;
}
}
this.Invoke(new MethodInvoker(delegate
{
if (bTrain) inputList.Items[i].SubItems[1].BackColor = Color.Red;
if (bPredic) inputList.Items[i].SubItems[2].BackColor = Color.Red;
if (bNetErro) inputList.Items[i].SubItems[0].BackColor = Color.Red;
if (bTrain | bPredic | bNetErro) { temp1.Text = "Erro"; temp1.BackColor = Color.Red; }
try { inputList.Items[i].SubItems[4] = temp1; }
catch (ArgumentOutOfRangeException) { inputList.Items[i].SubItems.Add(temp1); }
}));
}
}
And this is the function with ParallelFor, the program stops working even when I try a very small example with only items.
private void tent1()
{
ParallelOptions options = new ParallelOptions();
options.MaxDegreeOfParallelism = 4;
String tempTrain = "";
String tempPredic = "";
String tempNet = "";
String tTrain = dtrainTextBox.Text;
String tPredic = dpredicTextBox.Text;
Boolean bTrain;
Boolean bPredic;
Boolean bNetErro;
int n;
int nNet = networkList.Items.Count;
Parallel.For(0, inputList.Items.Count,
i =>
{
ListViewItem.ListViewSubItem temp1 = new ListViewItem.ListViewSubItem();
temp1.BackColor = Color.LightGreen;
temp1.Text = "NORMAL";
Console.WriteLine(i);
this.Invoke(new MethodInvoker(() =>
{
inputList.Items[i].SubItems[0].BackColor = Color.White;
inputList.Items[i].SubItems[1].BackColor = Color.White;
inputList.Items[i].SubItems[2].BackColor = Color.White;
tempTrain = String.Format("{0}\\{1}", tTrain, inputList.Items[i].SubItems[1].Text);
tempPredic = String.Format("{0}\\{1}", tPredic , inputList.Items[i].SubItems[2].Text);
tempNet = (String)inputList.Items[i].SubItems[0].Tag;
}));
bTrain = (!File.Exists(tempTrain));
bPredic = (!File.Exists(tempPredic));
bNetErro = !(int.TryParse(tempNet, out n));
if (!bNetErro)
{
if (!(n < nNet))
{
bNetErro = true;
}
}
this.Invoke(new MethodInvoker(delegate
{
if (bTrain) inputList.Items[i].SubItems[1].BackColor = Color.Red;
if (bPredic) inputList.Items[i].SubItems[2].BackColor = Color.Red;
if (bNetErro) inputList.Items[i].SubItems[0].BackColor = Color.Red;
if (bTrain | bPredic | bNetErro) { temp1.Text = "Erro"; temp1.BackColor = Color.Red; }
try { inputList.Items[i].SubItems[4] = temp1; }
catch (ArgumentOutOfRangeException) { inputList.Items[i].SubItems.Add(temp1); }
}));
});
}
How can I copy all items of the listview to a List without reference? I think adding/modify directly into the listview and the invokes are slowing the function so creating: List<ListViewItem> tList
I would modify everything in the tList and then I would use inputList.Items.AddRange(tList.ToArray());
This would remove all the invokes inside the loop.
ListViewItem[] tItemsTemp = null;
this.Invoke(new MethodInvoker(() =>
{
tItemsTemp = new ListViewItem[inputList.Items.Count];
inputList.Items.CopyTo(tItemsTemp, 0);
nList = inputList.Items.Count;
nNet = menuNetwork.Items.Count;
tTrain = dtrainTextBox.Text;
tPredic = dpredicTextBox.Text;
}));
List<ListViewItem> tList = new List<ListViewItem>(tItemsTemp);
ListViewItem[] tItems = (ListViewItem[]) tItemsTemp.Clone();
//Modifies the list or array of listviewitems
this.Invoke(new MethodInvoker(delegate
{
inputList.Items.Clear();
// Just one of them,not the 3,just showing how i would call them.
inputList.Items.AddRange(tItems);
inputList.Items.AddRange(tItemsTemp);
inputList.Items.AddRange(tList.ToArray());
}));
But tItemsTemp,tItems,tList were all references...
How can I copy with creating a reference?
Mixing UI and parallelization is not guaranteed to provide good results. The UI is a bottleneck by itself because it runs on a unique thread. Worse, yours includes some disk IO operations (File.Exists).
There are some things you may want to try anyway:
Enclose the whole loop with an inputList.BeginUpdate and an inputList.EndUpdate. This prevents the list to refresh each time you add an item.
Try SynchronizationContext and the Post method instead of the old fashioned Invoke. This might make the UI invocation smoother. See this answer to check how it works.
By the way, you can remove the first Invoke, if it is possible for you to move the three lines that ends with .backcolor = Color.White in the second Invoke.
If it is not better, then try to separate the process of building the items and displaying them :
Use parallelization fill a List of items to update (not with a ForEach, but with .AsParallel() against a LINQ query)
Use a loop to update your ListView.
If this is not satisfying, you will probably have to find a workaround using a ListView in virtual mode. This answer gives some hints to fill a virtual ListView an asynchronous way.

Updating TreeView after changing CellRendererCombo (Gtk#)

Could somebody point me in the right direction on how to update a Gtk.TreeView after changing a CellRendererCombo in Gtk#?
Since the only example I found was in Python, I tried to port the example to C#, but without success so far.
The Python example is here: http://learngtk.org/pygtk-tutorial/cellrenderercombo.html
In the code below I am having difficulties with the method ComboChanged.
After changing the value in the combobox (by selecting a different value) and placing the focus outside of the combobox, the value does not change.
using System;
using Gtk;
using System.Collections.Generic;
public partial class MainWindow: Gtk.Window
{
public MainWindow (): base (Gtk.WindowType.Toplevel)
{
Build ();
var tvComboBox = InitTreeViewWithComboBox ();
var vbox = new Gtk.VBox ();
vbox.PackStart (tvComboBox, true, true, 0);
this.Add (vbox);
this.ShowAll ();
}
// adopted from http://learngtk.org/pygtk-tutorial/cellrenderercombo.html
ListStore liststore_hardware;
ListStore liststore_manufacturers;
private TreeView InitTreeViewWithComboBox ()
{
liststore_manufacturers = new Gtk.ListStore(typeof (string));
var manufacturers = new List<string> {"Sony", "LG", "Panasonic", "Toshiba", "Nokia", "Samsung"};
foreach (var item in manufacturers) {
liststore_manufacturers.AppendValues (item);
}
liststore_hardware = new Gtk.ListStore(typeof (string), typeof (string));
liststore_hardware.AppendValues ("Television", "Samsung");
liststore_hardware.AppendValues ("Mobile Phone", "LG");
liststore_hardware.AppendValues ("DVD Player", "Sony");
var treeview = new Gtk.TreeView ();
treeview.Model = liststore_hardware;
var column_text = new TreeViewColumn { Title = "Text" };
var column_combo = new TreeViewColumn { Title = "Combo" };
treeview.AppendColumn (column_text);
treeview.AppendColumn (column_combo);
var cellrenderer_text = new CellRendererText ();
column_text.PackStart (cellrenderer_text, false);
column_text.AddAttribute (cellrenderer_text, "text", 0);
var cellrenderer_combo = new CellRendererCombo ();
cellrenderer_combo.Editable = true;
cellrenderer_combo.Model = liststore_manufacturers;
cellrenderer_combo.TextColumn = 0;
column_combo.PackStart (cellrenderer_combo, false);
column_combo.AddAttribute (cellrenderer_combo, "text", 1);
cellrenderer_combo.Edited += ComboChanged;
return treeview;
}
void ComboChanged (object o, EditedArgs args)
{
// Not really sure what to do here....
/*
var crc = o as CellRendererCombo;
TreeIter iter; // index within the combobox
if (!crc.Model.GetIterFirst (out iter)) {
return;
}
crc.Model.SetValue (iter, 0, args.NewText);
liststore_hardware.SetValue (iterHardware, 1, args.NewText);
*/
}
Going through the API a bit more I found the solution...:
void ComboChanged (object o, EditedArgs args)
{
TreeSelection selection = treeview.Selection;
TreeIter iter;
if (!selection.GetSelected (out iter)) {
return;
}
liststore_hardware.SetValue (iter, 1, args.NewText);
}

Optimizing large switch statement

I have large switch statement in which I create UIElements based on input value from XElement:
public static UIElement CreateElement(XElement element) {
var name = element.Attribute("Name").Value;
var text = element.Attribute("Value").Value;
var width = Convert.ToDouble(element.Attribute("Width").Value);
var height = Convert.ToDouble(element.Attribute("Height").Value);
//...
switch (element.Attribute("Type").Value) {
case "System.Windows.Forms.Label":
return new System.Windows.Controls.Label() {
Name = name,
Content = text,
Width = width,
Height = height
};
case "System.Windows.Forms.Button":
return new System.Windows.Controls.Button() {
Name = name,
Content = text,
Width = width,
Height = height
};
//...
default:
return null;
}
}
I am creating a lot controls like this and as you can see, too much repetition is going on.
Is there some way to avoid this repetition? Thanks in advance for ideas.
You could create a generic function that does the create:
private static Create<T>(string name, string text, double width, double height) where T: Control, new()
{
return new T { Name = name, Content = text, Width = width, Height = height }
}
Your switch then becomes:
switch (element.Attribute("Type").Value) {
case "System.Windows.Forms.Label" : return Create<System.Windows.Forms.Label>(name, text, width, height);
etc.
}
You could also adapt this to pass in the XElement, whichever you prefer.
If the Type attribute is always the name of the System.Type you want, then you could just do
Control ctrl = (Control) Activator.CreateInstance(Type.GetType(element.Attribute("Type").Value));
ctrl.Name = name;
etc.
If there's a one to one mapping between the value of the attribute and the type you want, then you can declare a readonly static field with the mapping:
private static readonly uiTypeMapping = new Dictionary<string,Type> {
{ "System.Windows.Forms.Label", typeof(System.Windows.Controls.Label) },
{ "System.Windows.Forms.Button", typeof(System.Windows.Controls.Button) },
{ etc. }
};
And use
UIElement elem = (UIElement) Activator.CreateInstance(uiTypeMapping[element.Attribute("Type").Value]);
etc.
Something like this could work... :)
var controlCreators = new Dictionary<string, Func<ContentControl>>
{
{"System.Windows.Forms.Label", () => new Label()},
{"System.Windows.Forms.Button", () => new Button()}
};
Func<ContentControl> createControl;
if (!controlCreators.TryGetValue(element.Attribute("Type").Value, out createControl))
{
return null;
}
var control = createControl();
control.Name = name;
control.Content = text;
control.Width = width;
control.Height = height;
return control;
Those different controls have inheritance trees. So for example Width, Height, Name are defined on FrameworkElement. So you could do something like the following:
object createdObject = null;
switch (element.Attribute("Type").Value)
{
case "System.Windows.Forms.Label":
createdObject = new System.Windows.Controls.Label();
break;
case "System.Windows.Forms.Button":
createdObject = new System.Windows.Controls.Button();
break;
}
var fe = createdObject as FrameworkElement;
if (fe != null)
{
fe.Name = element.Attribute("Name").Value;
fe.Width = Convert.ToDouble(element.Attribute("Width").Value);
fe.Height = Convert.ToDouble(element.Attribute("Height").Value);
}
var ce = createdObject as ContentElement;
if (ce != null)
{
ce.Content = element.Attribute("Value").Value;
}
return createdObject;
Note that by using this approach, in comparison to Flynn's answer, you can also easily add code such as "when the control is an ItemsControl, do this", i.e. code which won't apply to every type, but only to some of them.
You can do it with reflection + expressions.
[TestClass]
public class UnitTest1
{
public class Creator
{
private static Dictionary<string,Func<XElement, Control>> _map = new Dictionary<string, Func<XElement,Control>>();
public static Control Create(XElement element)
{
var create = GetCreator(element.Attribute("Type").Value);
return create(element);
}
private static Expression<Func<XElement, string>> CreateXmlAttributeAccessor(string elementName)
{
return (xl => xl.Attributes(elementName).Select(el => el.Value).FirstOrDefault() ?? "_" + elementName);
}
private static Func<XElement, Control> GetCreator(string typeName)
{
Func<XElement, Control> existing;
if (_map.TryGetValue(typeName, out existing))
return existing;
// mapping for whatever property names you wish
var propMapping = new[]
{
new{ Name = "Name", Getter = CreateXmlAttributeAccessor("Name") },
new{ Name = "Content", Getter = CreateXmlAttributeAccessor("Value") },
};
var t = Assembly.GetAssembly(typeof (Control)).GetType("System.Windows.Controls." + typeName);
var elementParameter = Expression.Parameter(typeof (XElement), "element");
var p = from propItem in propMapping
let member = t.GetMember(propItem.Name)
where member.Length != 0
select (MemberBinding)Expression.Bind(member[0], Expression.Invoke(propItem.Getter, elementParameter));
var expression = Expression.Lambda<Func<XElement, Control>>(
Expression.MemberInit(Expression.New(t),p), elementParameter);
existing = expression.Compile();
_map[typeName] = existing;
return existing;
}
}
[TestMethod]
public void TestMethod1()
{
var xel = new XElement("control",
new XAttribute("Type", "Button"),
new XAttribute("Name", "Foo"),
new XAttribute("Value", "Bar"),
new XElement("NonExistent", "foobar")); // To check stability
var button = (Button) Creator.Create(xel);
Assert.AreEqual("Foo", button.Name);
Assert.AreEqual("Bar", button.Content);
}
}
To make it work with other types then string, you can use Expression.Convert. Left as an exercise.
You could to this with reflection instead, or you could create a Dictionary of strings (what you're switching on now) and Funcs (or actions rather) where you create the controls.
for the specific code you posted, you can assign height and width after the switch statement, since they exsist on Control directly.

Categories