I am trying to create a Gantt Chart generator, using the Share point control:
<Sharepoint:JsGrid>
I followed this tutorial: How to: Create a Gantt Chart Using the JS Grid Control
I also linked my Sharepoint TaskList as the data Source.
I developped a system of filters using some XML.
But I now want to manage predecessors and represent dependencies by an arrow.
To manage them, I used the last parameter of the EnableGantt function (ganttDependentsColumnName), which one just need the name of the column which contains the dependency information.
What I have to put in this column ?
What I tried is to fill it with the ID of the task, the lane of the DataRow containing predecessors, and I tried to put an object of the class Dependency :
class Dependency : IJsonSerializable
{
public object Key {get; set;} // RecordKey
public LinkType{get; set;} //LinkType
public string ToJson(Serializer s)
{
return JsonUtility.SerializeToJsonFromProperties(s,this);
}
}
(This code is from the answers in the tutorial)
In the Key, what do I have to put? If someone did it or know how to do it, It could be nice.
Not sure if you are still facing this issue. But this is what we did for the Predecessors column and this is as far as I understand this:
1] Change the Dependency class a bit to add a constructor as shown (otherwise it errors out).
2] Then, you basically need to pass a Dependency array to the Predecessors column which means that the JSGrid control needs to know the starting point/row and the ending point/row of the dependency (thus an array is required). JSON Serialization is taken care of already because of the inheritance and the ToJson methods so no worries there.
Code:
1] Dependency Class:
public class Dependency : IJsonSerializable
{
public object Key { get; set; } // recordKey
public LinkType Type { get; set; } // SP.JsGrid.LinkType
public Dependency() {
Key = DBNull.Value;
Type = LinkType.FinishStart;
}
public string ToJson(Serializer s)
{
return JsonUtility.SerializeToJsonFromProperties(s, this);
}
}
2] Add column if not targeting a SharePoint Task List (where data is an object of DataTable):
data.Columns.Add(new DataColumn("Predecessors",typeof(Dependency[])));
3] Set the right object array to the Predecessors column:
if (<<A predecessor exists>>){
Dependency[] dep = new Dependency[2];
dep[0] = new Dependency();
try{
/*
// Unique Identifier for your row based on what you are
// passing to the GridSerializer while initializing it
// (as a third parameter which is called keyColumnName)
// In my case I had to get it by doing some coding as
// shown. The first object in the array represents the
// previous row and so the unique identifier should
// point to the previous row
*/
dep[0].Key = (
data.Select(
"ID=" +
data.Rows[s]["PredecessorsID"].ToString()
)
[0]["Key"]
);
}catch (Exception ex){
dep[0].Key = DBNull.Value;
}
dep[0].Type = LinkType.FinishStart;
/*
// Unique Identifier for your row based on what you are
// passing to the GridSerializer while initializing it
// (as a third parameter which is called keyColumnName)
// In my case I had to get it by doing some coding as
// shown. The second object in the array represents the
// current row and so the unique identifier should
// point to the current row
*/
dep[1] = new Dependency();
try{
dep[1].Key = data.Rows[s]["Key"];
}catch (Exception ex){
dep[0].Key = DBNull.Value;
}
dep[1].Type = LinkType.StartFinish;
data.Rows[s]["Predecessors"] = dep;
}
Finally, pass the Predecessors column while calling the EnableGantt() function:
gds.EnableGantt(
Convert.ToDateTime(
dr["start Date"]
),
Convert.ToDateTime(
dr["Due Date"]
),
GanttUtilities.GetStyleInfo(),
"Predecessors"
);
Make sure that your StartFinish and FinishStart link types matches the correct rows and that your tasks are listed correctly with correct task start dates and task end dates and predecessor keys.
Related
Hello i want to change and alter values inside the cache of my acumatica cache i would like to know how to do it
for example i want to change the Ext. Cost value pro grammatically of the first line or the second line or can i check if there is already a "Data Backup" on transaction Descr.
public delegate void PersistDelegate();
[PXOverride]
public void Persist(PersistDelegate baseMethod)
{
if (Globalvar.GlobalBoolean == true)
{
PXCache cache = Base.Transactions.Cache;
APTran red = new APTran();
red.BranchID = Base.Transactions.Current.BranchID;
red.InventoryID = 10045;
var curyl = Convert.ToDecimal(Globalvar.Globalred);
red.CuryLineAmt = curyl * -1;
cache.Insert(red);
}
else
{
}
baseMethod();
}
this code add a new line on persist but if it save again it add the same line agaub u wabt ti check if there is already a inventoryID =10045; in the cache
thank you for your help
You can access your cache instance by using a view name or cache type. Ex: (Where 'Base' is the graph instance)
Base.Transactions.Cache
or
Base.Caches<APTran>().Cache
Using the cache instance you can loop the cached values using Cached, Inserted, Updated, or Deleted depending on which type of record you are looking for. You can also use GetStatus() on an object to find out if its inserted, updated, etc. Alternatively calling PXSelect will find the results in cache (PXSelectReadOnly will not).
So you could loop your results like so:
foreach (MyDac row in Base.Caches<MyDac>().Cache.Cached)
{
// logic
}
If you know the key values of the cache object you are looking for you can use Locate to find by key fields:
var row = (MyDac)Base.Transactions.Cache.Locate(new MyDac
{
MyKey1 = "",
MyKey2 = ""
// etc... must include each key field
});
As Mentioned before you can also just use a PXSelect statement to get the values.
Once you have the row to update the values you set the object properties and then call your cache Update(row) before the base persist and you are good to go. Similar if needing to Insert(row) or Delete(row).
So in your case you might end up with something like this in your persist:
foreach (APTran row in Base.Transactions.Cache.Cached)
{
if (Globalvar.GlobalBoolean != true || row.TranDesc == null || !row.TranDesc.Contains("Data Backup"))
{
continue;
}
//Found my row
var curyl = Convert.ToDecimal(Globalvar.Globalred);
row.CuryLineAmt = curyl * -1;
Base.Transactions.Update(row);
}
I have 3 Excel files containing data related to Client details, Company of Stocks and Order Details of Stocks Purchase. I want to parse all the data into a Multi-layer Dictionary using C# and run "Sorting" and "Searching" Functions on the same. I am a novice when it comes to C# and was wondering what would be the code for the same.
Data Eg: Stock Symbol Company Name S&P Sector
AAPL Apple Inc. IT
I could be barking up the wrong tree but with what you've given us to work with, I'm assuming you want to take the data matrix in the relevant worksheet and from that data, create an enumerable list of data with the relevant type so you can perform operations over it like sorting, filtering, etc. If that's what you want then the below is an example of that.
This is the workbook I created with some test data ...
You said you're a novice with C#, but, to make the below work, create a new .NET Framework project and add the NuGet package ... Microsoft.Office.Interop.Excel. I called the project InterExcelDotNet but you can change that to be whatever you want.
using Microsoft.Office.Interop.Excel;
using System.Collections.Generic;
using System.Linq;
namespace ExcelInteropDotNet
{
public class CompanyStockInfo
{
public string StockSymbol { get; set; }
public string CompanyName { get; set; }
public string SPSector { get; set; }
}
class Program
{
static void Main(string[] args)
{
// Change the below variabes to the relevant values for your needs.
string workbookName = #"c:\temp\Source Data.xlsx";
string worksheetName = "CompanyStockData";
// Create a new list with the type being the CompanyStockInfo type.
var companyStockInfoList = new List<CompanyStockInfo>();
// Create an instance of Excel, open the workbook, fetch the sheet and then
// find the last row in column A.
var xlApplication = new Application();
var xlWorkbook = xlApplication.Workbooks.Open(workbookName, ReadOnly: true);
var xlSrcSheet = xlWorkbook.Worksheets[worksheetName] as Worksheet;
var lastRow = xlSrcSheet.Cells[xlSrcSheet.Rows.Count,1].End[XlDirection.xlUp].Row;
// There may be a better way to do this but essentially, the below will loop through
// all cells from the 2nd row to the last row and create a new item in the list
// that stores all of the data.
for (long row = 2; row <= lastRow; row++)
{
companyStockInfoList.Add(new CompanyStockInfo()
{
StockSymbol = (xlSrcSheet.Cells[row, 1] as Range).Text,
CompanyName = (xlSrcSheet.Cells[row, 2] as Range).Text,
SPSector = (xlSrcSheet.Cells[row, 3] as Range).Text
});
}
xlApplication.Quit();
// You can use Linq to sort and search the list for the data you're wanting to
// get your hands on.
// Will filter all entries that have Inc. in the company name.
var filteredList = companyStockInfoList.Where(item => item.CompanyName.Contains("Inc."));
// Orders all entries by the company name in alphabetical order.
var orderedList = companyStockInfoList.OrderBy(item => item.CompanyName);
}
}
}
Now, having given you the above, you should understand that the Excel library in C# does allow you to perform operations over the workbook directly like you can do within excel, like SORT and FILTER. That may be another way to achieve what you're wanting.
Sort
AdvancedFilter
I'm not sure if all of that helps or not but I hope it does.
Good luck ...!
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();
}
}
I'm thinking of a way to create a link or reference to a List of Strings. My situation is that I'm creating ARP table and I need to save IP (as String) of my interface that captured response msg. Interface's IP address is saved in List<String>.
ARP_Table_entry(System.Net.IPAddress host_ip_addr, System.Net.NetworkInformation.PhysicalAddress host_mac_addr, int position)
{
this.host_ip_addr = host_ip_addr;
this.host_mac_addr = host_mac_addr;
this.time = System.DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
this.local_inter = ??;
}
What I don't want to do, is to assign local_inter smt like list.ElementAt(0), because when I change IP address on the interface (List gets updated w/ new one), value in entry won't change - and I would have to do foreach for every entry (not bad, but...)
Instead I'm looking for solution, that will "link" that specific List-element to local_inter parameter - so changing IP in List will result in automatic update in every entry that contained old one.
If you can control code for ARP_Table_entry just make local_inter property that returns value from that mysterious list:
class ARP_Table_entry
{
List<string> mysteriousList;
int pos;
public ARP_Table_entry(List<string> mysteriousList, int pos,...)
{
this.mysteriousList = mysteriousList;
this.pos = pos;
...
}
// TODO: add null check/position verification as needed
string local_inter => mysteriousList[pos];
// or {get { return mysteriousList[pos];} for C# 5 and below
...
You can also use Func<string> as type or local_inter if you want to use fields for some reason:
class ARP_Table_entry
{
public Func<string> local_inter;
...
public ARP_Table_entry(List<string> mysteriousList, int pos,...)
{
local_inter = () => mysteriousList[pos];
...
}
Note that either approach will not protect you from replacing list altogether with originalMysteriousList = new List<string>().
Another option is to have more complex type to store list of IPs that will notify about its changes (similar to ObesrvableCollection) and update fields on change in the collection.
I am trying to write a program that prints out (in a string variable) the following information about an mdb database:
Table Name
Total number of columns of the table
List of columns as follows:
Column Name:
Column Data Type:
To accomplish this I used two custom types (public classes) and of course, lists. Here is the code I have so far (which by the way has been adjusted not in small part thanks to questions and answers gathered here):
Here are the classes I created to define the two new types I am using:
public class ClmnInfo
{
public string strColumnName { get; set; }
public string strColumnType { get; set; }
}
public class TblInfo
{
public string strTableName { get; set; }
public int intColumnsQty { get; set; }
public List<ClmnInfo> ColumnList { get; set; }
}
Here is the code that actually gets the data. Keep in mind that I am using OleDB to connect to the actual data and everything works fine, except for the problem I will describe below.
As a sample, I am currently testing this code with a simple 1 table db, containing 12 columns of type string save for 1 int32 (Long Int in Access).
//Here I declare and Initialize all relevant variables and Lists
TblInfo CurrentTableInfo = new TblInfo();
ClmnInfo CurrentColumnInfo = new ClmnInfo();
List<TblInfo> AllTablesInfo = new List<TblInfo>();
//This loop iterates through each table obtained and imported previously in the program
int i = 0;
foreach (DataTable dt in dtImportedTables.Tables)
{
CurrentTableInfo.strTableName = Globals.tblSchemaTable.Rows[i][2].ToString(); //Gets the name of the current table
CurrentTableInfo.intColumnsQty = dt.Columns.Count; //Gets the total number of columns in the current table
CurrentTableInfo.ColumnList = new List<ClmnInfo>(); //Initializes the list which will house all of the columns
//This loop iterates through each column in the current table
foreach (DataColumn dc in dt.Columns)
{
CurrentColumnInfo.ColumnName = dc.ColumnName; // Gets the current column name
CurrentColumnInfo.ColumnType = dc.DataType.Name; // Gets the current column data type
CurrentTableInfo.ColumnList.Add(CurrentColumnInfo); // adds the information just obtained as a member of the columns list contained in CurrentColumnInfo
}
//BAD INSTRUCTION FOLLOWS:
AllTablesInfo.Add(CurrentTableInfo); //This SHOULD add The collection of column_names and column_types in a "master" list containing the table name, the number of columns, and the list of columns
}
I debugged the code and watched all variables. It works great (the table name and column quantity gets registered correctly, as well as the list of column_names, column_types for that table), but when the "bad" instruction gets executed, the contents of AllTablesInfo are not at all what they should be.
The table name is correct, as well as the number of columns, and the columns list even has 12 members as it should have, but each member of the list is the same, namely the LAST column of the database I am examining. Can anyone explain to me why CurrentTableInfo gets overwritten in this manner when it is added to the AllTablesInfo list?
You're creating a single TblInfo object, and then changing the properties on each iteration. Your list contains lots of references to the same object. Just move this line:
TblInfo CurrentTableInfo = new TblInfo();
to the inside of the first loop, and this line:
ClmnInfo CurrentColumnInfo = new ClmnInfo();
inside the nested foreach loop, so that you're creating new instances on each iteration.
Next:
Important
Make sure you understand why it was failing before. Read my article on references if you're not sure how objects and references (and value types) work in C#
Use camelCased names instead of CamelCased ones for local variables
Consider using an object initializer for the ClmnInfo
Change your type names to avoid unnecessary abbreviation (TableInfo, ColumnInfo)
Change your property names to avoid pseudo-Hungarian notation, and make them PascalCased
Consider rewriting the whole thing as a LINQ query (relatively advanced)
The pre-LINQ changes would leave your code looking something like this:
List<TableInfo> tables = new List<TableInfo>();
int i = 0;
foreach (DataTable dt in dtImportedTables.Tables)
{
TableInfo table = new TableInfo
{
Name = Globals.tblSchemaTable.Rows[i][2].ToString(),
// Do you really need this? Won't it be the same as Columns.Count?
ColumnCount = dt.Columns.Count,
Columns = new List<ColumnInfo>()
};
foreach (DataColumn dc in dt.Columns)
{
table.Columns.Add(new ColumnInfo {
Name = dc.ColumnName,
Type = dc.DataType.Name
});
}
tables.Add(table);
// I assume you meant to include this?
i++;
}
With LINQ:
List<TableInfo> tables =
dtImportedTables.Tables.Zip(Globals.tblSchemaTable.Rows.AsEnumerable(),
(table, schemaRow) => new TableInfo {
Name = schemaRow[2].ToString(),
// Again, only if you really need it
ColumnCount = table.Columns.Count,
Columns = table.Columns.Select(column => new ColumnInfo {
Name = column.ColumnName,
Type = column.DataType.Name
}).ToList()
}
}).ToList();
You have only created one instance of TblInfo.
It's because you only have a single instance of TblInfo, which you keep updating in your loop and then add another reference to it to the List. Thus your list has many references to the same object in memory.
Move the creation of the CurrentTableInfo instance inside the for loop.