I have some Razor code that has conditional logic based on whether a certain array variable is set. I'm having a heck of a time figuring out the best way to handle when it's null and I'm not happy with my kludgy solution.
Here's a simple example of what's happening:
#{
if (ViewBag.Foo != null)
{
double[] bar = new double[ViewBag.Foo.Length];
}
}
Later on in the code, I'll have something like this:
#if (ViewBag.Foo != null)
{
...some code that uses `bar` goes here ...
}
With that code, I get errors when ViewBag.Foo actually is null. I get an exception thrown complaining about the second section of code that uses bar and it not being in scope . However, in the execution, that second section will always be skipped.
After messing with it for awhile, I just did this instead:
double[] bar;
#{
if (ViewBag.Foo != null)
{
bar = new double[ViewBag.Foo.Length];
}
}
else
{
bar = new double[1];
}
With this change, the code works when ViewBag.Foo is null and non-null. There's gotta be a better way to handle this... anyone?
This sort of work does not belong in a view:
#{
if (ViewBag.Foo != null)
{
double[] bar = new double[ViewBag.Foo.Length];
}
}
The sort of problem you had is exactly why this is the case. Your problem came down to the fact that bar was not correctly scoped. If instead this work was happing within a ViewModel, a similar mistake would have immediately given you a compiler error. Instead you don't find out until you compile and use the application, and the error you are given can often be cryptic and difficult to track down.
You solved your own problem.
The bar variable was not in scope because you declared it within an if block. Your change broadened it.
Related
I'm new to programming, C# Blazor, and RenderFragments in general, so forgive me if this is a basic question.
I need to set up a tabbed dialog interface where different tabs appear based on which menu buttons are clicked. I set up a DevExpress menu item which appears as a button:
<DxMenuItem Text="States" IconCssClass="homeMenuButtonStates" CssClass="menuItemSm" Click="OnStatesBrowseClick" />
When the user clicks it, the OnStatesBrowseClick function is called:
public void OnStatesBrowseClick()
{
tabName[tdiIndex] = "States";
TdiNewTab(tabName[tdiIndex]);
}
As you can see, its job is to assign a value to a member of a string array (tdiIndex was assigned "0"), and call the TdiNewTab method with argument tabName[tdiIndex] which evaluates to "States."
TdiNewTab has a switch statement which, for now, only has a case for "States."
public void TdiNewTab(string switchTabName) {
switch (switchTabName)
{
case "States":
renderStatesBrowsePage(tdiIndex);
break;
}
tdiIndex++;
}
In the event that "States" comes up, it points to renderStatesBrowsePage which should create a tab.
private RenderFragment renderStatesBrowsePage(int index)
{
deleteMe++;
RenderFragment item = tabsChildContent =>
{
deleteMe++;
tabsChildContent.OpenComponent<DxTabPage>(0);
tabsChildContent.AddAttribute(2, "Text", $"Tab # {index}");
tabsChildContent.AddAttribute(3, "ChildContent", (RenderFragment)((tabPageChildContent) =>
{
tabPageChildContent.OpenComponent<States>(0);
tabPageChildContent.CloseComponent();
}));
tabsChildContent.CloseComponent();
};
return item;
}
However, nothing happens. Using breakpoints, I determined that the block within "RenderFragment item = tabsChildContent =>" does not get executed. Indeed, when I put both "deleteMe++"s in there, only the first one incremented the variable.
I'm at a loss as to why the block won't execute. Any advice would be much appreciated!
In response to answer from Mister Magoo:
I'm now trying to call the RenderFragment from within my markup:
<DxMenuItem Text="States" IconCssClass="homeMenuButtonStates" CssClass="menuItemSm" Click="StatesBrowseClick">
#if (statesBrowse == 1)
{
#renderStatesBrowsePage(0);
}
</DxMenuItem>
#code
public void StatesBrowseClick()
{
int statesBrowse = 1;
}
However, nothing still happens. I made sure statesBrowse, which was initialized to "0" outside of the method, equaled 1 after the click. I also tried removing the #if statement (leaving just "#renderStatesBrowsePage(0);", and I got the same result.
Note: I'm using VS 2019, and in StatesBrowseClick, it tells me that "The variable 'statesBrowse' is assigned, but its value is never used." This seems inconsequential, though, as I have tried manually setting it to "1" and removing the "if" statement.
The block won't execute because there is nothing executing it.
renderStatesBrowsePage returns a RenderFragment that needs to be rendered - but you are discarding it here:
public void TdiNewTab(string switchTabName) {
switch (switchTabName)
{
case "States":
// This returns a RenderFragment but you are discarding it
renderStatesBrowsePage(tdiIndex);
break;
}
tdiIndex++;
}
It is hard to tell you what you should be doing without seeing more of your project, but generally a RenderFragment is rendered by adding it to the component markup
<div>some stuff</div>
<div>
#MyRenderFragment
</div>
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:
Can somebody provide an overview for use of the XamlBindingHelper class with examples? Specifically the GetDataTemplateComponent and SetDataTemplateComponent method.
In the official document, it says
This class is for use in code that is generated by the XAML compiler.
This tells me that I should be able to find some reference of it in code-generated classes (.g.cs) by x:Bind, given there's not a single thread on the Internet that explains what exactly it does.
So I created a test UWP project with a ListView, and inside its ItemTemplate I threw in some x:Bind with x:Phase. After I compiled the project, I found some of its methods used inside my MainPage.g.cs -
XamlBindingHelper.ConvertValue
public static void Set_Windows_UI_Xaml_Controls_ItemsControl_ItemsSource(global::Windows.UI.Xaml.Controls.ItemsControl obj, global::System.Object value, string targetNullValue)
{
if (value == null && targetNullValue != null)
{
value = (global::System.Object) global::Windows.UI.Xaml.Markup.XamlBindingHelper.ConvertValue(typeof(global::System.Object), targetNullValue);
}
obj.ItemsSource = value;
}
Apparently the XamlBindingHelper.ConvertValue method is for converting values. I knew this already, as I used it in one of my recent answers on SO.
XamlBindingHelper.SuspendRendering & XamlBindingHelper.ResumeRendering
public int ProcessBindings(global::Windows.UI.Xaml.Controls.ContainerContentChangingEventArgs args)
{
int nextPhase = -1;
switch(args.Phase)
{
case 0:
nextPhase = 1;
this.SetDataRoot(args.Item);
if (!removedDataContextHandler)
{
removedDataContextHandler = true;
((global::Windows.UI.Xaml.Controls.StackPanel)args.ItemContainer.ContentTemplateRoot).DataContextChanged -= this.DataContextChangedHandler;
}
this.initialized = true;
break;
case 1:
global::Windows.UI.Xaml.Markup.XamlBindingHelper.ResumeRendering(this.obj4);
nextPhase = -1;
break;
}
this.Update_((global::System.String) args.Item, 1 << (int)args.Phase);
return nextPhase;
}
public void ResetTemplate()
{
this.bindingsTracking.ReleaseAllListeners();
global::Windows.UI.Xaml.Markup.XamlBindingHelper.SuspendRendering(this.obj4);
}
XamlBindingHelper.SuspendRendering & XamlBindingHelper.ResumeRendering look very interesting. They seem to be the key functions to enable ListView/GridView's incremental item rendering which helps improve the overall panning/scrolling experience.
So apart from x:DeferLoadingStrategy and x:Load(Creators Update), they are something else that could be used to improve your app performance.
IDataTemplateComponent & IDataTemplateExtension
However, I couldn't find anything related to GetDataTemplateComponent and SetDataTemplateComponent. I even tried to manually set this attached property in XAML but the get method always returned null.
And here's the interesting bit. I later found this piece of code in the generated class.
case 2: // MainPage.xaml line 13
{
global::Windows.UI.Xaml.Controls.Grid element2 = (global::Windows.UI.Xaml.Controls.Grid)target;
MainPage_obj2_Bindings bindings = new MainPage_obj2_Bindings();
returnValue = bindings;
bindings.SetDataRoot(element2.DataContext);
element2.DataContextChanged += bindings.DataContextChangedHandler;
global::Windows.UI.Xaml.DataTemplate.SetExtensionInstance(element2, bindings);
}
break;
The method DataTemplate.SetExtensionInstance looks very similar to XamlBindingHelper.SetDataTemplateComponent. It takes element2 which is the root Grid inside the ItemTemplate of my ListView, and an IDataTemplateExtension; where the latter takes an element and an IDataTemplateComponent. If you have a look at their definitions, their functionalities are very similar, which makes me think if DataTemplate.SetExtensionInstance is the replacement of XamlBindingHelper.SetDataTemplateComponent? I'd love to know if otherwise.
Unlike IDataTemplateComponent, you can get an instance of the IDataTemplateExtension in your code -
var firstItemContainer = (ListViewItem)MyListView.ContainerFromIndex(0);
var rootGrid = (Grid)firstItemContainer?.ContentTemplateRoot;
var dataTemplateEx = DataTemplate.GetExtensionInstance(rootGrid);
In my case, the dataTemplateEx is an instance of another generated class called MainPage_obj2_Bindings, where you have access to methods like ResetTemplate and ProcessBindings.
I assume they could be helpful if you were to build your own custom list controls, but other than that I just can't see why you would ever need them.
So, I am doing this, wanting to be cool:
List<CheckButton> cbuttons = new List<CheckButton>(
new CheckButton[] {
cbShowAll,
cbShowApproved,
cbShowNew,
cbShowQC,
cbShowSent });
cbuttons.ForEach(b => b.LookAndFeel.UseDefaultLookAndFeel = false);
Is there a more cool way to do it?
EDIT:
There might be CheckButtons that won't be affected. There might be multiple property and/or method calls per group.
To make it applicable for many such controls like TextBox or CheckBox, create an extension method on the Form like so:
public static class FormExtensions
{
public static void ChangeAll<T>(this Form form, string propName, object value) where T : Control
{
foreach (Control c in form.Controls.OfType<T>())
{
PropertyInfo myPropInfo = typeof(T).GetProperty(propName);
if (myPropInfo != null)
{
myPropInfo.SetValue(c, value, null);
}
}
}
}
and we can call it:
this.ChangeAll<CheckBox>("Checked", false);
this.ChangeAll<Button>("Text", "DefaultButtonName");
this.ChangeAll<TextBox>("Text", "DefaultText");
where this is Form1 : Form
Unless that list is dynamic, doing it the “boring” way will be likely more efficient:
cbShowAll.LookAndFeel.UseDefaultLookAndFeel = false;
cbShowApproved.LookAndFeel.UseDefaultLookAndFeel = false;
cbShowNew.LookAndFeel.UseDefaultLookAndFeel = false;
cbShowQC.LookAndFeel.UseDefaultLookAndFeel = false;
cbShowSent.LookAndFeel.UseDefaultLookAndFeel = false;
Other than that, you could subclass the CheckButton and make that the default value too.
Also, in general “readable and possibly efficient” is much better than “cool and fancy”.
If you're performing some one-off initialization on a particular subset of controls (rather than all of them), then you could create an array containing that subset and then enumerate the array:
foreach (CheckButton button in
new[] { cbShowAll, cbShowApproved, cbShowNew, cbShowQC, cbShowSent })
{
button.LookAndFeel.UseDefaultLookAndFeel = false;
}
Using a loop allows you to avoid duplicate code (as in poke's answer).
Note: Eric Lippert wrote a blog post explaining why he prefers foreach over ForEach. Excerpt:
[Providing a ForEach extension method] lets you rewrite this perfectly clear code:
foreach (Foo foo in foos) { statement involving foo; }
into this code:
foos.ForEach((Foo foo) => { statement involving foo; });
which uses almost exactly the same characters in slightly different order. And yet the second version is harder to understand, harder to debug, and introduces closure semantics, thereby potentially changing object lifetimes in subtle ways.
Also, not all versions of the .NET Framework support List<T>.ForEach. For example, if you're writing a Windows Store app, you need to use foreach instead.
If you want to do that with all the CheckBoxes you have inside of a form or control, you don't need to hard code the array. If you're hard coding everything, then I agree with poke's answer.
However, you could be totally dynamic and if this is something you want to be reusing in many places, you could have a utility method (or something in a base form or control class that fires after the InitializeComponents() method).
If you want that, you could do something like this:
this.Controls.OfType<CheckBox>()
.ForEach(b => b.LookAndFeel.UseDefaultLookAndFeel = false);
If you don't want to do that with every CheckBox, you could create a subclass of those, and substitute that class name in the OfType generic declaration.
I'm working on an existing class that is two steps derived from System.Windows.Forms.Combo box.
The class overrides the Text property thus:
public override string Text
{
get
{
return this.AccessibilityObject.Value;
}
set
{
if (base.Text != value)
{
base.Text = value;
}
}
}
The reason given for that "get" is this MS bug: http://support.microsoft.com/kb/814346
However, I'm more interested in the fact that the "if" doesn't work.
There are times where "base.Text != value" is true and yet pressing F10 steps straight to the closing } of the "set" and the Text property is not changed.
I've seen this both by just checking values in the debugger, and putting a conditional breakpoint on that only breaks when the "if" statement's predicate is true.
How on earth can "if" go wrong?
The class between this and ComboBox doesn't touch the Text property. The bug above shouldn't really be affecting anything - it says it's fixed in VS2005. Is the debugger showing different values than the program itself sees?
Update
I think I've found what is happening here.
The debugger is reporting value incorrectly (including evaluating conditional breakpoints incorrectly). To see this, try the following pair of classes:
class MyBase
{
virtual public string Text
{
get
{
return "BaseText";
}
}
}
class MyDerived : MyBase
{
public override string Text
{
get
{
string test = base.Text;
return "DerivedText";
}
}
}
Put a breakpoint on the last return statement, then run the code and access that property.
In my VS2005, hovering over base.Text gives the value "DerivedText", but the variable test has been correctly set to "BaseText".
So, new question: why does the debugger not handle base properly, and how can I get it to?
Use String.Compare for comparing strings. There are subtleties with strings. I cannot tell you why the if would fail, other than that your strings might not really be 'equal'
... and this just about wraps up my new question. Ah well.