In my WPF program, I have an area of C# code that is quite repetitive, and it looks like this:
labelFirst = EnglishPicker.SelectedColorText.Substring(0, 1);
labelLast = EnglishPicker.SelectedColorText.Substring(3, 6);
label = labelFirst + labelLast;
UpdateSetting("English", label);
labelFirst = PhotographyPicker.SelectedColorText.Substring(0, 1);
labelLast = PhotographyPicker.SelectedColorText.Substring(3, 6);
label = labelFirst + labelLast;
UpdateSetting("Photography", label);
So I had a thought, is it possible to put this in a for loop something like this:
string[] names = {"English","Photography"};
foreach (string name in names)
{
labelFirst = (name +"Picker").SelectedColorText.Substring(0, 1);
}
Solution for those seeking help
Using a SharedMethod, I was able to shorten the code down as EnglishPicker and PhotographyPicker are the same class. Code:
private void GetPickerLabel(PickerClass picker){
labelFirst = picker.SelectedColorText.Substring(0,1);
labelLast = picker.SelectedColorText.Substring(3,6);
return labelFirst + labelLast;
}
UpdateSetting("English",GetPickerLabel(EnglishPicker));
UpdateSetting("Photography",GetPickerLabel(PhotographyPicker));
Thanks for those who helped me. :)
I think you almost nailed the simplest approach to this in your question, but using strings to refer to the pickers can break at run-time.
Try this instead:
Picker[] pickers = { EnglishPicker, PhotographyPicker};
foreach (Picker picker in pickers)
{
labelFirst = picker.SelectedColorText.Substring(0, 1);
}
The advantage here is that you have compile-time correctness and you can explicitly include or exclude any pickers you like.
It would be easy to turn the control back into text via the .Name property to get the setting updated.
Providing EnglishPicker and PhotographyPicker are properties:
You can use Reflection:
IEnumerable<string> names = new [] { "English", "Photography" };
foreach (string name in names)
{
// Build the property name
string propertyName = String.Concat(name, "Picker");
// Get Picker instance by locating the correct property
object picker = this.GetType().GetProperty(propertyName).GetValue(this);
// Get the SelectedColorText value from that instance
string colorText = (string) picker.GetType().GetProperty("SelectedColorText").GetValue(picker);
string first = colorText.Substring(0, 1);
string last = colorText.Substring(3, 6);
string label = String.Concat(first, last);
// Call UpdateSetting
UpdateSetting(name, label);
}
Flexibility comes with a price.
Everything, from the property lookup to the method invocation will be done in runtime, so watch out! It'll compile fine but it might break when you run it.
For instance, if you were to mistype "Enlgish" instead of "English", the code would compile perfectly. But, at runtime, the code will try to locate EnlgishPicker property and it'll fail.
Also, the above code is not defensive at all.
You should update it to include null checks to avoid potential exceptions.
How about an interface? No reflection needed at all. That's what they're for.
public interface ISelectedColorContainer {
string SelectedColorText {get;}
}
An Implementation
(I have no idea what your classes look like, but that's why we use interfaces!)
public class EnglishPickerImplementation:ISelectedColorContainer {
public string SelectedColorText {get {return "sunflower";}}
}
public class PhotographyPickerImplementation:ISelectedColorContainer {
public string SelectedColorText {get {return "chartreuse";}}
}
So you'd have a function that looks like this:
void SomeMethod(){
ISelectedColorContainer items = new ISelectedColorContainer[] {
new EnglishPickerImplementation(), new PhotographyPickerImplementation()};
foreach(var item in items){
var labelFirst = UpdateSetting(item);
var labelLast = item.SelectedColorText.Substring(3, 6);
var label = labelFirst + labelLast;
}
}
string UpdateSetting(ISelectedColorContainer input){
return input.SelectedColorText.Substring(0, 1);
}
Sorry if there are slight syntax errors, I didn't put this in an IDE.
Instead of a reflection-based solution, I would simply refactor the code to move the shared code in a separate method.
Example:
SharedMethod(EnglishPicker, "English");
SharedMethod(PhotographyPicker, "Photography");
// ...
private void SharedMethod(PickerClass picker, string settingName)
{
var labelFirst = picker.SelectedColorText.Substring(0, 1);
var labelLast = picker.SelectedColorText.Substring(3, 6);
var label = labelFirst + labelLast;
UpdateSetting(settingName, label);
}
Related
My code sets icon svgs like this:
Current.Resources["HomePageIcon5"] = (new[] {
"resource://Japanese.Resources.5_Light.svg",
"resource://Japanese.Resources.5_Gray.svg",
"resource://Japanese.Resources.5_Dark.svg" })[thc];
It does the same for many more icons and the same string
"resource://Japanese.Resources."
Appears many times.
Is there a way I could create a local method that could do this for me? What I am looking for is something I could call like this:
Current.Resources["HomePageIcon5"] = X("5_Light","5_Gray","5_Dark");
something like:
private static readonly _resourcesPath = "resource://Japanese.Resources.";
public void FillResource(string key, string value, int idx)
{
var content = new[]
{
_resourcesPath + value + "_Light.svg",
_resourcesPath + value + "_Gray.svg",
_resourcesPath + value + "_Dark.svg"
}
Current.Resources[key] = content[idx];
}
and use it like:
FillResource("HomePageIcon5", "5", thc);
The exact implementation for your request will be the following:
public string[] GetFormattedResources(params string[] strings)
{
const string STRING_FORMAT = "resource://Japanese.Resources.{0}.svg";
return strings.Select(str => string.Format(STRING_FORMAT, str)).ToArray();
}
This function uses the params keyword for grouping the strings and LINQ functions to handle the collection easily. (For example: Select)
The call will be as follows:
Current.Resources["HomePageIcon5"] = GetFormattedResources("5_Light","5_Gray","5_Dark");
I've been building an email generator function. It started as a regex function, but quickly moved over to reflection to make it as generic as possible. The idea is to have the email generator function pull in information from a messagedata class. It started as a simple task, as the function only had to change out a few static items, but as the function got more complex and the templates being sent out needed tables of information, then the function I had built was not enough.
I've extended the function to use a foreach loop to run through the template and replace text based on a list from the messageData class. I've been trying to get the list created in messageData to implement it into the emailGenerator function.
I've got:
string value = match.Groups["value"].Value;
// Code removed to shorten length
var items = (IEnumerable) value;
But it's not gathering the information from the messageData class. I'm thinking that maybe I need to get the value into a list?
Here is the EmailGenerator function:
public class EmailGenerator : IEmailGenerator
{
private string mergeTemplate(string template, object obj)
{
var operationMatches = operationParser.Matches(template).Cast<Match>().Reverse().ToList();
foreach (var match in operationMatches)
{
string operation = match.Groups["operation"].Value;
string value = match.Groups["value"].Value;
var propertyInfo = obj.GetType().GetProperty(value);
object dataValue = propertyInfo.GetValue(obj, null);
if (operation == "endforeach")
{
string foreachToken = "$foreach " + value + "$";
var startIndex = template.LastIndexOf(foreachToken, match.Index);
var templateBlock = template.Substring(startIndex + foreachToken.Length, match.Index - startIndex - foreachToken.Length);
var items = (IEnumerable) value;
string blockResult = "";
foreach (object item in items)
{
blockResult += this.mergeTemplate(templateBlock, item);
}
template = template.Remove(startIndex, match.Index - startIndex).Insert(startIndex, blockResult);
}
}
}
And here is the messageData class. It gets the information from a DTO.
** EDIT: Removed unnecessary code.
public class messageData : IMailObject
{
public List<messageItemData> Items
{
get
{
var items = new List<messageItemData>();
foreach (var docDTO in this.recipientDTO.InfoList)
{
items.Add(new messageItemData(docDTO));
}
}
}
}
public class messageItemData
{
// Properties
}
What I'm trying to accomplish is that the emailGenerator function is made generic enough to be reusable for other email templates later down the road, gathering the replacement information from the messageData class and the list it contains.
So, I finally found the answer. The code was 99% working. It was as simple as changing var items = (IEnumerable) value; for var items = (IEnumerable) dataValue;
I'm new in developing acumatica I am stuck at getting the value of a custom TextEdit field that I created. I can get all of the built-in field value through this code
InventoryItem items = (InventoryItem)Base.Item.Cache.Current;
but I cannot get the one that I have created at acumatica customization
here is the field I want to get
https://i.stack.imgur.com/gPln4.png
I already tried
InventoryItem items = (InventoryItem)Base.ItemSettings.Cache.Current;
var shortdesc = items.UsrShortDescription;
But it's not working and does not show the value inside the textbox
thank you in advance for helping
InventoryItem items = (InventoryItem)Base.ItemSettings.Current;
var itemExt = PXCache<InventoryItem>.GetExtension<InventoryItemExt>(items);
var shortdesc = itemExt.UsrShortDescription;
Vardan showed one way, for completeness of picture want to show another as well:
InventoryItem items = (InventoryItem)Base.ItemSettings.Current;
var itemExt = items.GetExtension<InventoryItemExt>();
This is an example of getting value from a non-extension field. I did not use extension DAC to add the Gift card field to the store setup screen.
In a method I need to get the value of that field. I should check whether the order contains Gift card item or not.
public static bool GiftcardName(OrderModel orders, BZWoocommerceStore store)
{
// "ZGift CArd W" => "giftcard"
string wooCommName = string.Empty;
string wooCommNameNoSpases = string.Empty;
bool containsGiftcardName = false;
bool isGiftcard = false;
foreach (OrderLineModel line in orders.LineItems)
{
string gNameInAcumatica = store.GiftcardIdentifier;
string gNameInAcumaticaWithoutSpaces = gNameInAcumatica.Replace(" ", "");
wooCommName = line.Name; //pattern
wooCommNameNoSpases = wooCommName.Replace(" ", "");
//wooCommNameNoSpases = new string(wooCommName.ToCharArray()
// .Where(c => !Char.IsWhiteSpace(c))
// .ToArray());
//woCommNameNoUperCase= wooCommNameNoSpases.ToLower();
//isGiftcardName= woCommNameNoUperCase.Contains(gName);
//containsGiftcardName = wooCommNameNoSpases.Contains(gName);
containsGiftcardName = Regex.IsMatch(wooCommNameNoSpases, gNameInAcumaticaWithoutSpaces, RegexOptions.IgnoreCase);
if(containsGiftcardName)
{
isGiftcard = true;
}
}
return isGiftcard;
}
So, when I call this method I give to that 2 arguments, orders and store.
The store argument was created in this way.
public PXSelect<BZWoocommerceOrder> Order;
In an action method I wrote this.
string storeCode = this.Order.Current.StoreCode;
BZWoocommerceStore store = PXSelect<BZWoocommerceStore, Where<BZWoocommerceStore.storeCode, Equal<Required<BZWoocommerceStore.storeCode>>>>.Select(this, storeCode);
My GiftcardName() method sees the value of original field. Writing "Original" I mean that you do not use any technique like this one.
BZSOOrderExt rowExt = sender.GetExtension<BZSOOrderExt>(row);
I created the following class:
class TrdRamValue
{
double Value = 0.0;
TrdState State = TrdState.Ok;
DateTime dt = DateTime.UtcNow;
}
I then created a list with this class to store the information:
List<TrdRamValue> DMSrows = new List<TrdRamValue> ();
And I use the following inside a Handler to constantly insert values every second:
string[] value = new string[3];
value[0] = val;
value[1] = val.Error.ToString ();
value[2] = val.Time.ToString ();
DMSrows.AddRange (value);
But in code it keeps saying that I have an error in my argument, that I can't convert string[] to System.Collections.Generic.IEnumerable.
I'm completely lost on this one...
ANSWER:
It was just a minor error from my part, and I also took huMpty duMpty suggestion since he's completely right, I don't need that string array.
All I had to do was make the class and the variables inside public in order to do what huMpty duMpty told me.
public class TrdRamValue
{
public double Value = 0.0;
public TrdState State = TrdState.Ok;
public DateTime dt = DateTime.UtcNow;
}
Then apply huMpty duMpty suggestion:
TrdRamValue value = new TrdRamValue() ;
value.Value = val;
if (!val.Error) {
value.State = TrdState.Ok;
}
else if (val.Error) value.State = TrdState.Error;
value.dt = val.Time;
DMSrows.Add (value);
Your List is not a List<string> but a List<TrdRamValue>. Therefore, you cannot add strings to the list. You can only add instances of TrdRamValue, or, in the case of AddRange, an IEnumerable (such as an array) of TrdRamValue.
So you can do this:
TrdRamValue toAdd = new TrdRamValue { Value = val, State = ..., dt = ... };
dmsRows.Add(toAdd);
(btw naming a variable DMSRows does not fit with the .net naming conventions).
You're trying to add strings to a list of TrdRamValue objects. Your list is type-safe, which means you are only allowed to add TrdRamValue objects to it.
Not sure why you need string array here.
Also you don't need List.AddRange here since you adding one item. You can use List.Add
DMSrows.Add(new TrdRamValue{
Value =val,
State =val.Error,
dt =val.Time
});
Any help here as I'm a C# noob. The following code works fine and returns 1 string ViewState2. I'd like it to return an array of ViewState2 and EventValidation2 so I can manipulate it later on. How would I convert the code below to return an array?
public string get_status(string local_fname)
{
var dts_doc = new HtmlAgilityPack.HtmlDocument();
dts_doc.Load(local_fname);
//Pull the values
var ViewState = dts_doc.DocumentNode.SelectSingleNode("/html[1]/body[1]/div[1]/input[4]/#value[1]");
var EventValidation = dts_doc.DocumentNode.SelectSingleNode("/html[1]/body[1]/div[2]/input[1]/#value[1]");
string ViewState2 = ViewState.Attributes[3].Value;
string EventValidation2 = EventValidation.Attributes[3].Value;
//Display the values
//System.Console.WriteLine(ViewState.Attributes[3].Value);
//System.Console.WriteLine(EventValidation.Attributes[3].Value);
//System.Console.ReadKey();
return ViewState2;
}
Don't use an array, but a class. Doing this, you don't have to remember what each element means.
public class Status
{
public string ViewState {get; set;}
public string EventValidation {get; set;}
}
using System;
using HtmlAgilityPack;
[...]
public Status GetStatus(string localFileName)
{
var dtsDoc = new HtmlDocument();
dtsDoc.Load(localFileName);
//Pull the values
var viewStateNode = dtsDoc.DocumentNode.SelectSingleNode("/html[1]/body[1]/div[1]/input[4]/#value[1]");
var eventValidationNode = dtsDoc.DocumentNode.SelectSingleNode("/html[1]/body[1]/div[2]/input[1]/#value[1]");
string viewState = viewStateNode.Attributes[3].Value;
string eventValidation = eventValidationNode.Attributes[3].Value;
//Display the values
//Console.WriteLine(viewState);
//Console.WriteLine(eventValidation);
//Console.ReadKey();
return new Status
{
ViewState = viewState,
EventValidation = eventValidation
}
}
Also, you should read up on coding guidelines and naming conventions in the C# language, also the using statement might be interesting. I have corrected some "mistakes", but probably didn't catch all. Also, I have renamed a couple of variables, to make their content clearer. You also might want to look into using the var keyword only in a loop, while using LINQ (or anomynous types in general) or with really long class names. Written out type names can increase readability quite a lot.
If you really want an array with ViewState2 and EventValidation2 in it, you can make the following changes:
// Notice: return value of string[] instead of string
public string[] get_status(string local_frame);
And:
// Notice: returning an array
return new string[] { ViewState2, EventValidation2 };
That said, this is really the "quick and dirty" approach, and is not really appropriate if you're going to want this code to be maintainable (when's the last time you read documentation on a function that "returns an array of length 2, with a string representing X as the first element and another string representing Y as the second"?).
Femaref's right; the correct thing to do would be to encapsulate the information you want returned in its own type.
Assuming you answer yes to this question (although I'd recommend a different approach, see below) this will do what you're asking:
public String[] get_status(string local_fname)
{
var dts_doc = new HtmlAgilityPack.HtmlDocument();
dts_doc.Load(local_fname);
//Pull the values
var ViewState = dts_doc.DocumentNode.SelectSingleNode("/html[1]/body[1]/div[1]/input[4]/#value[1]");
var EventValidation = dts_doc.DocumentNode.SelectSingleNode("/html[1]/body[1]/div[2]/input[1]/#value[1]");
string ViewState2 = ViewState.Attributes[3].Value;
string EventValidation2 = EventValidation.Attributes[3].Value;
String[] retValues = new String[2];
retValues[0] = ViewState2;
retValues[1] = EventValidation2;
return retValues;
//Display the values
//System.Console.WriteLine(ViewState.Attributes[3].Value);
//System.Console.WriteLine(EventValidation.Attributes[3].Value);
//System.Console.ReadKey();
return ViewState2;
}
That said, I would follow the approach afte the line.
I'd write a class that has the data members you want:
public class DataClass
{
public string ViewState { get; set; }
public string EventValidation { get; set; }
}
Then I'd modify the method to return an instance of your data class.
public DataClass get_status(string local_fname)
{
var dts_doc = new HtmlAgilityPack.HtmlDocument();
dts_doc.Load(local_fname);
//Pull the values
var ViewState = dts_doc.DocumentNode.SelectSingleNode("/html[1]/body[1]/div[1]/input[4]/#value[1]");
var EventValidation = dts_doc.DocumentNode.SelectSingleNode("/html[1]/body[1]/div[2]/input[1]/#value[1]");
var dc = new DataClass();
dc.ViewState = ViewState.Attributes[3].Value;
dc.EventValidation = EventValidation.Attributes[3].Value;
return dc;
}
string[] array = new string[2];
array[0] = ViewState2;
array[1] = EventValidation2;
return array;
But it seems to trivial as answer. Please Does it solve your problem? If no, can you specify better the question please?