I have a Header / Detail custom screen where I'm manipulating which grid columns display based on a dropdown selection in the header. This works fine, but now I'd like to change a few column names as well. Using the documented syntax, I cannot get this to work. I can't see what I'm doing wrong - nothing seems to make any difference. I've attached to process and put a break point at this event, and it's hitting the line - but the system seems to just ignore it:
protected virtual void ACMappingHeader_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
var mh = (ACMappingHeader)e.Row;
if (mh == null) return;
if (mh.MappingType == "Option1")
{
PXUIFieldAttribute.SetDisplayName<ACMappingDetail.target1CD>(this.MappingDetail.Cache, "Target");
Thanks...
Your display name routine looks correct however to make sure the column names actually update you need to do the following:
In the page source, you need to set the "RepaintColumns=true" value on the grid. This can be done via customization manager or directly from your ASPX source. - This tells the grid to refresh the columns after a callback allowing the headers to actually redisplay.
You can refer below example to dynamically change Grid Column Header.
Below example works with Screen PM401000 – Project Transactions Inquiry
public class TransactionInquiryExt : PXGraphExtension<TransactionInquiry>
{
public void TranFilter_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected baseInvoke)
{
if (baseInvoke != null)
baseInvoke(cache, e);
PX.Objects.PM.TransactionInquiry.TranFilter row = e.Row as PX.Objects.PM.TransactionInquiry.TranFilter;
if (row == null) return;
PXUIFieldAttribute.SetDisplayName<PMTran.description>(Base.Transactions.Cache,
row.ProjectID.HasValue ? "Description for Project Tran" : "Description");
}
}
Make sure to set RepaitColumns property to True of PXGrid Control.
Related
As seen in the image below, I want to try and pass the value of the Selector to the Attention field. I'm trying to make it so that whenever I choose a new selector value, the Attention field will be updated with the selector's value.
The Contact Selector in the image above is a Custom Field, so I was trying to access it through it's extension. However, I couldn't seem to get it working.
Here is the Data Access screen showing how the field is set up:
Here is the code so you can grab it if needed:
[PXDBString(50)]
[PXUIField(DisplayName="Contact")]
[PXSelector(typeof(Search2<Contact.displayName,
LeftJoin<BAccount, On<BAccount.bAccountID, Equal<Contact.bAccountID>>>,
Where<Contact.contactType, Equal<ContactTypesAttribute.person>>>))]
[PXRestrictor(typeof(
Where<Current<PMContact.customerID>,
Like<Contact.bAccountID>>), "")]
Below are my two attempts of trying to grab the extension. I've tried using these pieces of code in various events; RowSelected, RowUpdated, FieldUpdated. Nothing seemed to work, which obviously means I'm not grabbing the extension properly, but I'm not sure what else to try.
Attempt 1
protected void PMContact_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
var row = (PMContact)e.Row;
if (row == null) return;
PMContactExt rowExt = row.GetExtension<PMContactExt>();
if (rowExt != null) {
row.Attention = rowExt.UsrContactSelect;
}
}
Attempt 2
protected void PMContact_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
var row = (PMContact)e.Row;
if (row == null) return;
PMContact items = (PMContact)Base.ItemSettings.Current;
var itemExt = PXCache<PMContact>.GetExtension<PMContactExt>(items);
row.Attention = itemExt.UsrContactSelect;
}
This attempt was giving me an error about the ItemSettings part:
\App_Code\Caches\ProjectEntry.cs(43): error CS1061: 'ProjectEntry' does not contain a definition for 'ItemSettings' and no accessible extension method 'ItemSettings' accepting a first argument of type 'ProjectEntry' could be found (are you missing a using directive or an assembly reference?)
I'm a bit stuck on what else I can try to make this happen.
Do you have any other suggestions? :)
Thanks so much for your help #Hugues and #Robert!
As Hugues mentioned, my custom field had CommitChanges=False so I changed it to true and voila, it worked!
It worked fine using the RowSelected event, but I took Robert's advice and changed it to FieldUpdated to ensure I'm doing things more suitably.
This is the code I used when the event was triggered:
protected void PMContact_UsrContactSelect_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
{
var row = (PMContact)e.Row;
if (row == null) return;
PMContactExt rowExt = row.GetExtension<PMContactExt>();
if (rowExt != null) {
row.Attention = rowExt.UsrContactSelect;
}
}
Thanks so much again! This issue has been a really stubborn one :D
Try using a Field updated event on your selector field. The row_selected is geared more for driving the behavior of the UI.
https://help-2021r1.acumatica.com/(W(1))/Help?ScreenId=ShowWiki&pageid=9048a6d5-41a0-a5bd-9b78-7ce9833114b2
ItemSettings dataview is in InventoryItemMaint graph, are you extending InventoryItemMaint?
Also it does not contain PMContact DAC, it is selecting InventoryItem DAC.
I would not expect this line of code to work for this reason:
PMContact items = (PMContact)Base.ItemSettings.Current;
I've tried using these pieces of code in various events; RowSelected, RowUpdated, FieldUpdated. Nothing seemed to work, which obviously means I'm not grabbing the extension properly,
I'm not sure about the conclusion here. Did you debug the code to make sure the PMContactExt extension is null?
Did you debug to make sure the events called? If events aren't called you need to add CommitChanges=True property on the ASPX control.
RowUpdated event should be used instead of RowSelected because RowSelected is not recommended to set DAC field values.
Is PMContact DAC a Projection? If it's a projection you will need to extend both the projection and the base DAC.
EDIT it is not a projection:
I have a vertically and horizontally scrollable DataGridView on a form.
I use virtual mode because the underlying datatable is huge.
When I scroll right, if the last column is not fully shown in the view, then I see repeated calls to CellValueNeeded.
How can I fix this?
My thoughts:
Why is CellValueNeed being repeatedly called for a partially visible column anyway? Perhaps I can fix the cause of this.
Within CelValueNeeded - can I detect it is partially visible and return without processing? Both "Displayed" and "Visible" are true when I check the cell values.
My code:
private void grid_Data_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
Console.WriteLine("CellValue: " + e.RowIndex + " " + e.ColumnIndex);
if (e.RowIndex > Grid.Rows.Count - 1)
return;
DataGridView gridView = sender as DataGridView;
e.Value = Grid.Rows[e.RowIndex][e.ColumnIndex];
gridView.Rows[e.RowIndex].HeaderCell.Value = (e.RowIndex).ToString();
}
EDIT1:
After Digitalsa1nt's answer I found a way to fix the issue. It is complicated because the first column is treated differently to the last column. AND it makes a difference if you are setting RowHeaders.
In CellValueNeed above, I now return if the following function is true.
private bool IsPartiallyVisible(DataGridView gridView, DataGridViewCellValueEventArgs e)
{
if (gridView.FirstDisplayedScrollingColumnIndex == e.ColumnIndex)
{
if (gridView.FirstDisplayedScrollingColumnHiddenWidth != 0)
{
return true;
}
}
bool sameWidth = gridView.GetColumnDisplayRectangle(e.ColumnIndex, false).Width == gridView.GetColumnDisplayRectangle(e.ColumnIndex, true).Width;
return !sameWidth;
}
Looking at the MSDN documentation for CellValueNeeded it reads as though it's a standard visual event, simply fires as soon as a cell becomes "visible", i don't think it defines the logic it uses to understand visual partiality. It just seems as though it tries to prepare for the cell to become fully "in-view". I suspect any intermediary states are not exposed.
That said there are some suggestions here (SO reply) and here (weird web-blog) that mention the use of DataGridView.GetColumnDisplayRectangle with the intention of determining if the rectangle of the cell is within the bounds of the screen.
Here's a snippet from the web blog:
The second parameter to GetColumnDisplayRectangle is called
CutOverFlow, which is a Boolean value that controls whether the
function returns the complete column rectangle (even if the column is
not completely visible) or only the portion of the column's rectangle
that is visible.
By calling this method twice, once with CutOverFlow set to true and
once with it set to false, you can create a function that compares the
results and returns a Boolean value when the column is only partially
visible:
Return dg.GetColumnDisplayRectangle(columnindex, False).Width = _
dg.GetColumnDisplayRectangle(columnindex, True).Width
This would allow you to stop processing when grid_Data_CellValueNeeded is called and the above returns false based on the last cells location.
Sorry for the poor quality of the title. I couldn't think of a better way to phrase this.
For a project I'm currently working on with a few friends, I got myself in the situation where I have created a dynamic form (with reflection) which I now want to validate.
Example (ignore the black box, it contains old form elements which are now irrelevant and i didn't want to confuse you guys):
As you may have guessed already, it is an application for creating a mysql database.
Which is where I get to my problem(s). I want to disable checkboxes if others are checked.
For example: If I check "PrimaryKey" I want to disable the checkbox "Null".
Changing from unsigned to signed changes the numericupdown minimum and maximum etc.
But with reflection and all, I find it difficult to know exactly which checkbox to disable.
I was hoping you guys would have some suggestions.
I have been thinking about this for a while and a few thoughts have come to mind. Maybe these are better solutions than the current one.
Thought 1: I create UserControls for every datatype. Pro's: no problems with reflection and easy identifying of every control in the UserControl for validation. Con's: Copy-Pasting, Lots of UserControls, with a lot of the same controls.
Thought 2: Doing something with the description tags for every property of the classes. Creating rules in the description that allow me to link the checkboxes together. Here I'll only have to copy the rules to every class property and then it should be ok.
I had been thinking of other solutions but I failed to remember them.
I hope you guys can give me a few good pointers/suggestions.
[Edit]
Maybe my code can explain a bit more.
My code:
PropertyInfo[] properties = DataTypes.DataTypes.GetTypeFromString(modelElement.DataType.ToString()).GetType().GetProperties();
foreach (PropertyInfo prop in properties)
{
if (prop.Name != "Label" && prop.Name != "Project" && prop.Name != "Panel")
{
var value = prop.GetValue(modelElement.DataType, null);
if (value != null)
{
tableLayoutPanel1.Controls.Add(new Label { Text = prop.Name, Anchor = AnchorStyles.Left, AutoSize = true });
switch (value.GetType().ToString())
{
case "System.Int32":
NumericUpDown numericUpDown = new NumericUpDown();
numericUpDown.Text = value.ToString();
numericUpDown.Dock = DockStyle.None;
tableLayoutPanel1.Controls.Add(numericUpDown);
break;
case "System.Boolean":
CheckBox checkBox = new CheckBox();
checkBox.Dock = DockStyle.None;
// checkbox will become huge if not for these changes
checkBox.AutoSize = false;
checkBox.Size = new Size(16, 16);
if (value.Equals(true))
{
checkBox.CheckState = CheckState.Checked;
}
tableLayoutPanel1.Controls.Add(checkBox);
break;
default:
MessageBox.Show(#"The following type has not been implemented yet: " + value.GetType());
break;
}
}
}
}
Here is a mockup from my comments:
// The ViewModel is responsible for handling the actual visual layout of the form.
public class ViewModel {
// Fire this when your ViewModel changes
public event EventHandler WindowUpdated;
public Boolean IsIsNullCheckBoxVisible { get; private set; }
// This method would contain the actual logic for handling window changes.
public void CalculateFormLayout() {
Boolean someLogic = true;
// If the logic is true, set the isNullCheckbox to true
if (someLogic) {
IsIsNullCheckBoxVisible = true;
}
// Inform the UI to update
UpdateVisual();
}
// This fires the 'WindowUpdated' event.
public void UpdateVisual() {
if (WindowUpdated != null) {
WindowUpdated(this, new EventArgs());
}
}
}
public class TheUI : Form {
// Attach to the viewModel;
ViewModel myViewModel = new ViewModel();
CheckBox isNullCheckBox = new CheckBox();
public TheUI() {
this.myViewModel.WindowUpdated += myViewModel_WindowUpdated;
}
void myViewModel_WindowUpdated(object sender, EventArgs e) {
// Update the view here.
// Notie that all we do in the UI is to update the visual based on the
// results from the ViewModel;
this.isNullCheckBox.Visible = myViewModel.IsIsNullCheckBoxVisible;
}
}
The basic idea here is that you ensure that the UI does as little as possible. It's role should just be to update. Update what? That's for the ViewModel class to decide. We perform all of the updating logic in the ViewModel class, and then when the updating computations are done, we call the UpdateVisual() event, which tells the UI that it needs to represent itself. When the WindowUpdated Event occurs, the UI just responds by displaying the configuration set up by the ViewModel.
This may seem like a lot of work to set up initially, but once in place it will save you tons and tons of time down the road. Let me know if you have any questions.
Try relating the event of one checkbox to disable the other; something like this:
private void primaryKeyBox_AfterCheck(object sender, EventArgs e)
{
nullBox.Enabled = false;
}
This is a very simple example and would have to be changed a bit, but for what I think you're asking it should work. You would also have to add to an event for the boxes being unchecked. You would also need logic to only get data from certain checkboxes based on the ones that are and are not checked.
For all the other things, such as changing the numbers based on the dropdown, change them based on events as well.
For WinForms I would use data binding.
Create an object and implement INotifyPropertyChanged and work with that object.
Then, If you have an object instance aObj:
To bind the last name property to a textbox on the form do this:
Private WithEvents txtLastNameBinding As Binding
txtLastNameBinding = New Binding("Text", aObj, "LastName", True, DataSourceUpdateMode.OnValidation, "")
txtLastName.DataBindings.Add(txtLastNameBinding)
Take a look here for more info.
INotifyPropertyChanged
I am working on claim expenses application for the staff where I work. Part of the process contains a listview, part of a new requirement is that if an expense type is mileage the user will not be able to edit the item, only delete and resubmit as part of business rules and UK tax reasons etc.
Anyway, I want to be able to find a control in each item of the listview that has a certain text value.
I thought something like the following but this is not correct and I know why.
Label ExpenseTypeLabel = (Label)Expenses.FindControl("ExpenseTypeLabel");
string ExpenseType = (ExpenseTypeLabel.Text.ToString());
if (ExpenseType == "Mileage")
{
foreach (ListViewDataItem thisItem in Expenses.Items)
{
ImageButton btnEdit = (ImageButton)thisItem.FindControl("btnEdit");
btnEdit.Enabled = false;
}
}
The expenses are based on weekending and as the page loads it throws my excepion as It cannot bind to a particular individual control as there are many ExpenseTypeLabels associated with the expense for the current weekending (which loads first).
What I am trying to accomplish here is to find all ExpenseTypeLabels in both the item template and the alternating item template and disable the edit function of that expense item. FYI incase you're wondering the weekending is the expense, and the children are the individual expense items.
Could one of you lovely people please educate me on the best way to accomplish this?
Thanks
Matt
Binding order, and timing for accessing bound items, is extremely important; this is especially true when you have sub controls that have binding items also.
If you want to affect the the display for these bound controls, you can usually do it from the aspx end.
Create a link from the front end to a function on the server end, then pass it all the necessary parameters:
<asp:listview id='lstExpense'>
...
<asp:button id='btnEdit' enabled='<%#= isEnabled(((Expense)Container.DataItem).ExpenseType) %>' ...
...
<asp:listview>
On the server end, make a public function to return that value:
public boolean IsEnabled(string ExpenseType) {
return ('Mileage' != ExpenseType);
}
Best solution though, is to use jQuery. Not exaggerating, but you can accomplish all of that with something as simple as:
$('.rowClass').each(function() {
if ($(this).find('.expenseTypeClass').val() == 'Mileage'))
$(this).find('.btnEditClass').attr('disabled','disabled');
})
use OnItemDataBound event as follows
OnItemDataBound="Expenses_ItemDataBound"
protected void Expenses_ItemDataBound(object sender, ListViewItemEventArgs e)
{
if (e.Item.ItemType == ListViewItemType.DataItem)
{
Label ExpenseTypeLabel = (Label)e.Item.FindControl("ExpenseTypeLabel");
string ExpenseType = (ExpenseTypeLabel.Text.ToString());
if (ExpenseType == "Mileage")
{
// disable button
}
}
}
I'm trying to find a good code sample to update a database entry in my listview control. I suppose I would need to extract the ID from somewhere (some label control?). I am using LINQtoSQL to talk with the database.
protected void lvTargets_ItemUpdating(object sender, ListViewUpdateEventArgs e)
{
InventoryDataContext inventory = new InventoryDataContext();
//Target target = from target in inventory.Targets
// where target.ID == lvTargets.Items[e.ItemIndex].FindControl("ID")
// *** Not sure how to go about this ^^^
//inventory.Targets.InsertOnSubmit(target);
//inventory.SubmitChanges();
lvTargets.EditIndex = -1;
BindInventory();
}
You can get the ID from the event arguments either like
e.Keys["ID"]
e.OldValues["ID"]
depending on your situation.