Acumatica Dynamic MultiSelect Dropdown - c#

I have a screen entry to store transaction data, I want to use dynamic with multiselect combobox to select status and status data is taken from the table, but when some of the data status is selected, the amount of stored data does not match that has been selected,
I have tried the following code, but it's doesn't work for me.
public class StatusMultiStringListAttribute : PXStringListAttribute
{
public StatusMultiStringListAttribute() : base()
{
PXResultset<StatusTable> rslt = PXSelect<StatusTable>.Select(new PXGraph());
List<string> values = new List<string>();
List<string> labels = new List<string>();
foreach (PXResult<StatusTable> item in rslt)
{
BSMTStatus e = (StatusTable)item;
values.Add(e.StatusID);
labels.Add(e.Description);
}
this._AllowedValues = values.ToArray();
this._AllowedLabels = labels.ToArray();
MultiSelect = true;
}
}
is there any other solution, sorry my English is bad, thanks.

I noticed your comment on http://asiablog.acumatica.com/2016/03/multiselect-combo-box.html and saw that you posted some additional code. Based on your sample code, I identified two problems:
First of all, the values you're loading from the StatusTable DAC contain trailing spaces which are not trimmed. You haven't provided the declaration of the StatusTable DAC, but it's safe to assume from your screenshot that this field has the IsFixed attribute set to true. With these settings, the system will add white space at the end of your value. To save space in the target field, I would recommend to add a Trim() to the constructor code:
foreach (PXResult<StatusTable> item in rslt)
{
BSMTStatus e = (StatusTable)item;
values.Add(e.StatusID.Trim()); //Remove any white-space
labels.Add(e.Description);
}
Second, the status field where you're storing the selected values is not long enough to accommodate multiple selections. It's currently defined as 20 characters ([PXDBString(20, IsFixed=true)]), and even assuming you remove the whitespace you would still be limited to 4 choices. I suggest you to change it to 255, and to also remove IsFixed=true since it's not needed for this field:
[PXDBString(255)]
[PXDefault]
[PXUIField(DisplayName = "Status")]
[StatusStringList]
public virtual string Status

Related

How to use AutonumberAttribute.getNextNumber()?

When I use the AutonumberAttribute.getNextNumber(), it gives me the next number of the sequence but it also make the next number to change.
IE if I call 2 time in a row:
nextNumber = AutoNumberAttribute.GetNextNumber(ARLetteringPiece.Cache, LetteringPiece, numbering, DateTime.Now);
first time i'll get "0000001"
second time i'll get "0000002"
I want to be able to know what the next number will be without modifying it's next value.
Is there a way to achieve this ?
Thanks a lot
Edit to answer the comments :
I have a custom table, my UI key is generated with Autonumbering, and I need to put this key in the lines of my other tables to "bind" them to my custom table. So I need to know what will be the autogenerated number.
It depends on the relationship between your DACs (tables).
You can solve this by using the PXDBChildIdentity in the fields of all the tables that need to store the new key.
For example, if your DAC's autonumber field is of type integer and is called MyDAC.MyAutonumberField.
You can add the attribute to all fields in your other DACs that need to store the value like this:
[PXDBInt()]
[PXDBChildIdentity(typeof(MyDAC.myAutonumberField))]
public virtual int? MyDACID { get; set; }
If the other DACs are "children" of your custom DAC you should use the PXParent attribute in all the child DACs on the field that references their parent like this:
[PXDBInt(IsKey = true)]
[PXDBDefault(typeof(MyDAC.myAutonumberField))]
[PXParent(typeof(Select<MyDAC,
Where<MyDAC.myAutonumberField,
Equal<Current<myAutonumberField>>>>))]
public virtual int? MyParentDacID { get; set; }
I managed to do it in another way : First I save my "header", then I update the lines with the value autogenerated for my header and then I save it again.
public static void createLettering(List<ARRegister> lines)
{
// We build a new LELettering piece
Lettrage graph = CreateInstance<Lettrage>();
LELettering piece = new LELettering();
piece.Status = ListStatus._OPEN;
piece.LetteringDateTime = DateTime.Now;
piece = graph.ARLetteringPiece.Insert(piece);
// We fill the checked lines with the autonumber of the piece
bool lineUpdated = false;
foreach (ARRegister line in lines)
{
if (line.Selected.Value)
{
if (!lineUpdated)
{
piece.BranchID = line.BranchID;
piece.AccountID = line.CustomerID;
piece = graph.ARLetteringPiece.Update(piece);
graph.Actions.PressSave();
}
line.GetExtension<ARRegisterLeExt>().LettrageCD = graph.ARLetteringPiece.Current.LetteringCD;
graph.ARlines.Update(line);
lineUpdated = true;
}
}
// If there are lines in our piece, we save it
// It saves our lettering piece and our modifications on the ARLines
if (lineUpdated)
{
graph.Actions.PressSave();
}
}

asp:gridview with a list as datasource, which also has a dictionary

I have a table with closing codes, for example:
Code Description
CL1 Reason 1
CL2 Reason 2
AF1 After 1 period
AF2 After 2 periods
Also a table with orders. Orders will be imported once a week and the table has, besides the other columns, a column holding the batch date and one holding the closing code. This last one can be NULL when the order isn’t closed or has one of the codes from the first given table.
I want to have a gridview which list per batch the total number of orders, the number of closed orders and per reason the number of orders involved.
Already made a class like this:
public class BatchSales
{
public DateTime BatchDate { get; set; }
public int BatchCount { get; set; }
public int TotalClosed { get; set; }
public Dictionary<string, int> Counters { get; set; }
}
And a method which returns a List of BatchSales.
Defined an asp:gridview in my aspx-page, with AutoGenerateColumns=false. I like to get this filled with 5 columns (batch date, count, closed, CL1, CL2, AF1 and AF2) and per row the relevant data.
In my code behind already added the first 3. My problem is adding the columns based on the dictionary. I’ve the closing codes read in a list and doing this:
foreach (var item in reasons)
{
BoundField bf = new BoundField();
bf.HeaderText = item.Description;
bf.DataField = "xxxx";
bf.DataFormatString = "{0:n0}";
bf.HeaderStyle.VerticalAlign = VerticalAlign.Top;
bf.ItemStyle.VerticalAlign = VerticalAlign.Top;
bf.ItemStyle.Wrap = false;
gvBatch.Columns.Add(bf);
}
What do I need to define for xxxx to get the value from the dictionary? Or isn’t this the right approach?
What you're wanting is more of a pivot grid. That aside, you need either an item template in the dictionary column that shows your dictionary data as rows (not ideal) or what I would do in this case is just have a button in that column that said something like "Show Details" and either have a modal window pop up with the list of counts or send it to a details page with the list of counts.

How to populate dependant checkboxlist in c#

I have three dependant checkboxlists.
1. Countries
2. States
3. Cities
I want to list all the States if the particular Country is selected in the Countries checkboxlist. And similarly if i select any State then the respective Cities should be populated in the cities checkboxlist.
I have created separate functions for States for every Country and calling them with the following code:
private void Country_SelectedIndexChanged(object sender, EventArgs e)
{
foreach (string s in Country.CheckedItems)
{
if (Country.CheckedItems.Contains("US"))
{
US_States_Checked();
}
if (Country.CheckedItems.Contains("Canada"))
{
Canada_States_Checked();
}
}
}
public void Canada_States_Checked()
{
string[] canada_states = new string[12];
canada_states[0] = "Alberta";
canada_states[1] = "British Columbia";
canada_states[2] = "Manitoba";
canada_states[3] = "New Brunswick";
canada_states[4] = "Newfoundland and Labrador";
canada_states[5] = "Northwest Territories";
canada_states[6] = "Nova Scotia";
canada_states[7] = "Ontario";
canada_states[8] = "Prince Edward Island";
canada_states[9] = "Quebec";
canada_states[10] = "Saskatchewan";
canada_states[11] = "Yukon Territory";
State.Items.AddRange(canada_states);
}
I have the following problems:
1. Which property is used for detecting when the checkbox is UnChecked?
2. How to make a check on the name of selected state/country and check whether it is Checked or Not? Something like:
if(country.selectedItem.Equals("US") and country.selectedItem is unchecked....)) {
.......
}
How to remove/clear the particular states/cities when the country is unchecked keeping in mind that it shouldn't remove the states of any other country listed in the states checkboxlist?
Problem is simple but a bit tricky.
Thanks
Separate function for each country is pretty bad idea. You should store data outside of your code. Consider using XML or database or JSON to store data. You can read about XML serialization here and here, for example
After loading data from file to memory you can place it in the dictionary like
Dictionary<string, Dictionary<string, List<String>>> data;
//access data
var cities = data["Canada"]["Ontario"];
And final part - pass selected item to your dictionary:
var states = data[country.selectedItem].Keys;
and
var cities = data[country.selectedItem][state.selectedItem];
I suppose that country and state are names of your CheckBoxList objects.
As for CheckBoxList usage - just implement your logic, all needed methods are described here. (I am still not sure, what are you using: WPF or WinForms or what, but you can find proper docs).
SelectedIndices and SelectedItems return selected elements of checkBoxList.
You can check like this:
var selectedItems = country.SelectedItems;
if (selectedItems.Contains("Canada")
{
//Do what you need
}

Why don't these ElasticSearch fields appear in "Fields"?

I can't figure this one out. I'm creating a new ElasticSearch index using the ElasticProperty attributes/decorators. Here's how I create the index:
client = ClientFactory(indexName);
if (!client.IndexExists(indexName).Exists)
{
var set = new IndexSettings() { NumberOfShards = 1 };
var an = new CustomAnalyzer() { Tokenizer = "standard" };
an.Filter = new List<string>();
an.Filter.Add("standard");
an.Filter.Add("lowercase");
an.Filter.Add("stop");
an.Filter.Add("asciifolding");
set.Analysis.Analyzers.Add("nospecialchars", an);
client.CreateIndex(c => c
.Index(indexName)
.InitializeUsing(set)
.AddMapping<ItemSearchable>(m => m.MapFromAttributes())
);
}
And all of my fields are being created properly from the attributes specified on the class, except these two. They are C# enumerations, which is maybe part of the problem. I'm trying to save them in the index as numeric fields...
[Required, ElasticProperty(Index = FieldIndexOption.NotAnalyzed, Store = true, NumericType = NumberType.Short, IncludeInAll = false)]
public Enums.PlatformType PlatformType { get; set; }
[ElasticProperty(Index = FieldIndexOption.NotAnalyzed, Store = true, NumericType = NumberType.Short, OmitNorms = true, IncludeInAll = false)]
public Enums.ItemType ItemType { get; set; }
When I set up the index and check via Kibana, I don't see PlatformType or ItemType at all in the list of fields in the empty index.
When I insert a record, I can see the values in the source (JSON) as numbers (as expected), but the "Fields" are not there.
So I'm thinking it's either because they're C# enum type, or because I'm trying to store it as a number. But I'm stumped on why Elasticsearch is skipping these fields. Many thanks for any ideas you may have.
UPDATE 1 ... The search still works (even without those fields being shown in the Fields section). I'm thinking it might just be a Kibana problem. In the Table view, it shows my two fields like this...
and hovering over those triangle exclamation marks says "No cache mapping for this field. Refresh your mapping from the Settings > Indices page". But of course I can't find such a page within Kibana.
So I might be fine behind the scenes and this is a non-issue. Does anyone else have any insight on what might fix this, make it clearer, or whether this is known behaviour and I should just move on? Thanks.

Changing selected item in GridViewComboboxColumn (Telerik, winforms)

I'm wondering how you can set the selected element of a gridviewcomboboxcolumn.
As a bit of a foreword: I'm using the column with autocomplete mode in order to
have autocomplete functionality, but I also want to add new elements to the list.
It worked so far without a hitch EXCEPT for one situation:
I already have:
T1
T12
T123
In the data source.
Then when I have for example T12 selected and do backspace to select T1
I've got the problem that I need to manually click on T1 in the list AS there is no selection of T1 as there are multiple possibilities shown. Thus when I leave the editor mode without manually selecting T1 I get T12 as selected item.
I want to change this behaviour in such a way that the first found item is pre selected (always). (regardless if it is a new element or a changed to element so to say)
Currently I've already added a custom handler for the cellendedit to add the new value to the list:
private void MainFormGridView_CellEndEdit(object Sender, GridViewCellEventArgs eventArgs)
{
var virtualizedCurrentCell = ((Telerik.WinControls.UI.GridVirtualizedCellElement)(currentCell));
var currentGridviewComboBoxColumn = ((Telerik.WinControls.UI.GridViewComboBoxColumn)(virtualizedCurrentCell.Data));
if (((List<string>)currentGridviewComboBoxColumn.DataSource).IndexOf((string)currentCell.Value) > -1)
{
foundValueInList = true;
}
if (!foundValueInList)
{
((List<string>)currentGridviewComboBoxColumn.DataSource).Add((string)currentCell.Value);
}
}
The column itself is created in this way (bnefore being added to the gridview it is a part of:
GridViewComboBoxColumn newColumn;
newColumn = new GridViewComboBoxColumn();
((GridViewComboBoxColumn)newColumn).DataSource = (from c in entity.myOffer
orderby c.customer
where c.customer!= null
select c.customer)
.Distinct()
.ToList();
((GridViewComboBoxColumn)newColumn).DropDownStyle = Telerik.WinControls.RadDropDownStyle.DropDown;
((GridViewComboBoxColumn)newColumn).AutoCompleteMode = AutoCompleteMode.SuggestAppend;
newColumn.FieldName = "customer";
newColumn.Name = "customer";
newColumn.HeaderText = "Customer";
newColumn.TextAlignment = System.Drawing.ContentAlignment.MiddleCenter;
newColumn.Width = 100;
listOfColumns.Add(newColumn);
this.MainFormGridView.Columns.Add(newColumn);
So the question is there what can I do to select specific items in the dropdownlist AND is the CellEndEdit the correct location for that (as I suspect)?
A possible solution for the problem itself is to forego the usage of the GridViewComboboxcColumn to get a field with autocomplete and instead use a "normal" GridViewTextBoxColumn.
In addition to this change we will need an customized editor which brings in the autocomplete element. One thing of note here is that the height of the cell changes slightly leading to chars being chopped off a bit if you don't have the setting active that rows can grow in size automatically (thus if that is not the case, then you need to increase the row height to at least 30:
Example of how to do this in this case: MainFormGridView.TableElement.RowHeight = 30; )
The creation code for the textbox with autocomplete now changes to the following:
GridViewTextBoxColumn newColumn;
newColumn = new GridViewTextBoxColumn();
newColumn.FieldName = "customer";
newColumn.Name = "customer";
newColumn.HeaderText = "Customer";
newColumn.TextAlignment = System.Drawing.ContentAlignment.MiddleCenter;
newColumn.Width = 100;
this.MainFormGridView.Columns.Add(newColumn);
Like I mentioned before we now need a custom editor for the autocomplete box itself:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Telerik.WinControls.UI;
namespace MyProject
{
class customAutoCompleteBox : RadTextBoxControlEditor
{
protected override Telerik.WinControls.RadElement CreateEditorElement()
{
return new RadAutoCompleteBoxElement();
}
public override void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
{
RadAutoCompleteBoxElement element = this.EditorElement as RadAutoCompleteBoxElement;
if (element.IsAutoCompleteDropDownOpen)
{
return;
}
base.OnKeyDown(e);
}
}
}
Then after this is done we only need to do 2 more steps to get the new autocomplete box to function:
1.) Setting the EditorRequired and CellEditorInitialized events
2.) Writing those events
For 1.) it is quite easy to do:
this.MainFormGridView.CellEditorInitialized += MainFormGridView_CellEditorInitialized;
this.MainFormGridView.EditorRequired += MainFormGridView_EditorRequired;
For 2.) the code here is also quite easy to do. Although I will put in a bit of a foreword here:
element.Delimiter can cause a few problems. Defaultwise one would use ' ' as delimiter there,
BUT if the data itself has many blanks inside that could raise an unexpected behaviour, as
each blank separated data part is seen as one "tag" or autocomplete element. Thus when the row is chosen you can get multiple tags displayed that you can delete one by one. In my case that was unwanted thus I did not use ' ' as separator but instead '\t' as the data does not have tabs inside.
Here is the code for CellEditorInitialized:
void MainFormGridView_CellEditorInitialized(object sender, GridViewCellEventArgs e)
{
if (e.ActiveEditor is customAutoCompleteBox)
{
customAutoCompleteBox editor = (customAutoCompleteBox)e.ActiveEditor;
RadAutoCompleteBoxElement element = (RadAutoCompleteBoxElement)editor.EditorElement;
element.Delimiter = '\t';
element.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
element.AutoCompleteDisplayMember = e.Column.Name;
element.AutoCompleteValueMember = e.Column.Name;
element.AutoCompleteDataSource = (from c in entity.myOffer
orderby c.customer
where c.customer!= null
select c.customer)
.Distinct()
.ToList();
}
}
And now for ViewCellFormatting
void MainFormGridView_EditorRequired(object sender, Telerik.WinControls.UI.EditorRequiredEventArgs e)
{
if (MainFormGridView.CurrentColumn.Name == "customer")
{
e.Editor = new customAutoCompleteBox();
}
}
With this an autocomplete is possible where the original problem does not occur. The only things not so nice here are that one has to press enter 2 times (after the first time the "tags" are shown from the row that was selected, and only the 2nd enter sets the tags. Same for using TAB to move out of the cell). Additionally the combobox is only shown after typing in a char and not already from the beginning.
In total, this solution functions but the behaviour (and appearance) of the autocompletebox is slightly different from the original combobox - autocomplete box.
Edit:
As it seems if you use the delimiter it adds the delimiter at the end, which can cause unwanted results (thus nothing found) if the autocomplete box is shown in an filter. A possible solution is using \0 as a delimiter so that nothing is added to the selected string from the autocomplete box.
Although this also makes it so that ALL chosen elements are treeated as being one single tag.

Categories