I'm adding a couple of controls as a child controls of another custom control I've developed. Here's where I add the child controls (a custom label and a generic span control):
public static void AddLabel(this IExtendedControl control, string inheritableCssClass = "")
{
TestCLabel contentLabel = new TestCLabel();
contentLabel.Text = control.LabelText;
control.Controls.Add(contentLabel);
if (control.Required)
{
HtmlGenericControl requiredFieldIndicator = new HtmlGenericControl("span");
requiredFieldIndicator.Attributes["class"] = "requiredFieldIndicator";
requiredFieldIndicator.InnerText = " *";
control.Controls.Add(requiredFieldIndicator);
}
and I then do the following in the render method of the parent control:
protected override void Render(HtmlTextWriter w)
{
base.Render(w);
foreach (Control c in this.Controls)
{
c.RenderControl(w);
}
if (Required)
{
rfv.RenderControl(w);
}
}
but I get the error 'An entry with the same key already exists'. This is being caused by the attempt to manually render the child controls. I don't think I should need to do the manual rendering, but before I coded this in the controls weren't appearing (nothing appears in the HTML markup). Any ideas what's going on?
Looks like the manual rendering of the child controls wasn't the issue. I'd reworked the code
as I was originally assigning the child controls to a property of the parent control but changed this to add the child controls to the controls collection of the parent control (so that the child controls now go through their correct life cycle). I'd previously been manually rendering the child controls when they were properties of the parent control and was still doing this. When manually rendering all the children, the controls were being added a second time. Still doesn't answer:
Why is the manual adding necessary, as this should be automatic?
Related
First part of my question
I am creating a UWP app which uses the NavigationView class and need a little help as to building a good approach as to how I manage control creation in code behind (not in a markup language).
I decided as opposed to adding controls in the markup language xaml that I would create a list of controls in C# and add these control as part of my NavView_Loaded method.
I understand there are benefits to simply working with xaml to create the UI. However, I intend to have various different circumstances under which controls are visible or collapsed and I find it easier to maintain the status of controls by working in code behind or in C# using my mainpage class.
Currently, I have something like the following
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
public ObservableCollection<NavigationViewItem> SetNavViewItems()
{
var navitems = new ObservableCollection<NavigationViewItem>
{
new NavigationViewItem()
{
Content = "Home",
Icon = new SymbolIcon(Symbol.Home),
Tag = "home",
Visibility = Visibility.Visible,
},
new NavigationViewItem()
{
Content = "Documents",
Icon = new SymbolIcon(Symbol.Document),
Tag = "documents",
Visibility = Visibility.Visible,
},
new NavigationViewItem()
{
Content = "Library",
Icon = new SymbolIcon(Symbol.Library),
Tag = "library",
Visibility = Visibility.Visible,
}
};
return navitemsHome;
}
private void NavView_Loaded(object sender, RoutedEventArgs e)
{
navitems = SetNavViewItems();
foreach (var navitem in navitems)
{
NavView.MenuItems.Add(navitem);
}
}
}
As I understand it, in this example I have used a strongly typed collection in order to contain a collection of NavigationViewItem's. My issue here is that I wish to create a collection, list or arraylist of various types of controls (i.e. NavigationViewItemHeader, NavigationViewItem, NavigationViewList, NavigationViewItemSeparator).... and I'm not sure what is most appropriate; a list, a collection (weakly typed), an arraylist or if there is a better option.
To round out my explanation.
I then have a separate list (of strings) which contains all of the navigation item tags which I use to test conditions and set whether a corresponding control is visible or not.
Second part of my question
One other UI control I'm struggling with which may or may not be fully implemented as part of the NavigationView class, is expanding an Item or Header to display a list of sub items. My current understanding is that NavigationViewItem is the most appropriate control to use as the main heading/item. The sub items should be using the NavigationViewList. I can then write a method for setting the visibility of the NavigationViewList controls which is triggered when the user clicks on the corresponding NavigationViewItem.
Third and final part
Whenever I create a control in my NavigationView (i.e. a stack panel), it appears to automatically create a NavigationViewItem with the content stack panel which has the default style (i.e. height). I expect that this is intended and any control created in the NavigationView inherets from the NavigationViewBase. Frustratingly I seem to have difficult displaying multiple rows of a stack panel or grid which inherets from NavigationViewBase. Can anyone shed some light? Is this in my imgaination or is there something important to understand when creating a control in the NavigationView class.
Any help much appreciated!!!
Thanks!
I have a custom control (let's say MyContainer) that simply is a ScrollViewer with a Canvas inside.
I'm able to add controls to MyContainer like in a Canvas but in XAML designer this controls aren't movable like in a normal Canvas; they can't be moved with the mouse.
Here's the MyContainer code:
[ContentProperty("Children")]
public class MyContainer : ScrollViewer, IAddChild
{
private Canvas _innerCanvas;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public UIElementCollection Children
{
get { return _innerCanvas.Children; }
}
public MyContainer()
{
this._innerCanvas = new Canvas();
this.Content = _innerCanvas;
this.Loaded += MyContainer_Loaded;
}
void MyContainer_Loaded(object sender, RoutedEventArgs e)
{
_innerCanvas.Height = this.ActualHeight;
_innerCanvas.Width = this.ActualWidth;
}
void IAddChild.AddChild(object value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
UIElement uie = value as UIElement;
if (uie == null)
{
throw new ArgumentNullException("value");
}
_innerCanvas.Children.Add(value as UIElement);
}
void IAddChild.AddText(string text)
{
;
}
}
Where am I wrong?
PS: please avoid replies like "don't use editor, use XAML code only"; I need to make a User Control usable via graphic interface.
I think that you did not choose the correct base class. In WPF there are certain extensibility points that you should use for certain types of UI elements and I guess that the designer is hard-wired to these classes.
The different types of UI Elements are:
Visuals: they usually derive from FrameworkElement and their purpose is to display something that the user normally does not interact with (e.g. a text block).
Controls: they represent something that the user can interact with, like buttons, check boxes, text boxes, scroll viewers, etc. They usually derive from Control or ContentControl.
Panels: their purpose is to layout other UI Elements, Grid or StackPanel are examples. They all derive from the Panel base class.
Items controls: they usually provide selection for a number of items. ListBox, ComboBox, and TreeView are examples for them. All of them derive from ItemsControl.
Another important thing is that ContentControls and ItemsControls can display any object, not only those that can render themselves. They use the WPF Data Templating mechanism for that (the default is calling ToString on a non-renderable object and putting the resulting string in a TextBlock).
According to your code, I would assume that you either want to implement a panel or an items control. For panels, you should know about the Measure - Arrange - Render cycle of WPF and how you can use it to layout the panel's children.
Implementing an items control is a little bit harder because essentially an items control uses items that wrap the actual content of each displayed object (e.g. ListBoxItem), a panel to layout these items, an items container generator to dynamically create the child items, and of course you can use styles and templates. Most of the items controls also incorporate a scroll viewer. If you want to learn more about items controls, I strongly encourage you to read the "Items Controls: A to Z" blog series by Dr. WPF.
I haven't tried it out but I'm sure if you choose the correct base class to extend from, then you can use your control with the designer properly.
It is a well known error if you try to add the RadAjaxManager to your page twice:
Only one instance of a RadAjaxManager can be added to the page
Telerik explains how you can solve this for design-time issues with a proxy control.
For most of the controls we use on pages, this error does not fire, even though each of these controls have a RadAjaxManager on them, sometimes even inside a repeater (accidentally, but still, the error doesn't throw). However, with one such control (a dynamic button) we have added it to several places on the page with no problem, possibly because this was all the same control, but nested in another control we receive the error above again, as soon as we add it to the page.
I have tried to solve it by:
adding the control dynamically to the page, but because control events fire before the page events, this leads to some dynamic behavior to not occur anymore.
adding the RadAjaxManager dynamically only once to the control, with built-in extra checks, like so:
private RadAjaxManager GetAjaxManager()
{
var ctl = this.FindControl("ajaxManager");
if (ctl != null)
{
return (RadAjaxManager)ctl;
}
// alternative method
var mgr = RadAjaxManager.GetCurrent(this.Page);
if (mgr != null)
{
return mgr;
}
// control is never found, always returns null
return null;
}
protected override void OnInit(EventArgs e)
{
if (this.GetAjaxManager() == null)
{
// ajax mgr is never found, and this always throws
// "cannot add multiple times" error
var ajaxManager = new RadAjaxManager();
ajaxManager.ID = "ajaxManager";
this.Controls.Add(ajaxManager);
}
}
Several variants of the above
The result is either: the control is never found and is therefore added more than once, resulting in the above error, or the control is added but too late in the process for the other controls in the usercontrol, resulting in several AJAX events not happening.
How should I add the RadAjaxManager to a user-control that is itself used inside several other user-controls, such that the manager only occurs once on the page and/or such that RadAjaxManager.GetCurrent actually works?
Plane A - add the RadAjaxManager to the page level, not to the user controls. Thus, the user controls can have RadAjaxManagerProxy controls, the static GetCurrent() method will work.
Plan B - use RadAjaxPanel controls if you want self-contained user controls. They have an ajaxRequest() client-side method and a server side event for that, and since user controls are usually smallish, you will likely be able to get away with a single panel for them.
Plan C - leave AJAX setup to the parent page. If a parent user control is already AJAX-enabled, its entire content will travel with the postback, so neesting more AJAX settings on inner user controls may not bring you a performance benefit.
Plan D - use only asp:UpdatePanel controls with UpdateMode=Conditional so you will have extremely fine-grained control of your partial rendering.
I'm trying to write a form theme class library to adjust the form layout in a simple way for any project I'll be working on.
This is basically an idea of what it should look like:
http://www.beaverdistrict.nl/form_layout.png
In essence, the plugin works as follows:
// form class, which inherits the plugin class
class FormToTheme : ThemedForm
{
public FormToTheme()
{
// some code here
}
}
// plugin class itself
class ThemedForm: Form
{
public ThemedForm()
{
// some code here
}
}
Basically I set the FormBorderStyle to None, and drew the layout by code.
But now, the controls that are added can be placed over the custom titlebar, which isn't possible in a normal form if you keep the default FormBorderStyle.
So I figured that I could work around this by automatically adding the controls to the content panel, instead of the usercontrol.
So what I tried to do was this:
private void ThemedForm_ControlAdded(Object sender, ControlEventArgs e)
{
// some simple code to set the control to the current theme I'm using
e.Control.BackColor = Color.FromArgb(66, 66, 66);
e.Control.ForeColor = Color.White;
// the code where I try to place the control in the contentPanel controls array,
// and remove it from it's parent's controls array.
if (e.Control.Name != contentPanel.Name)
{
e.Control.Parent.Controls.Remove(e.Control);
contentPanel.Controls.Add(e.Control);
}
}
But when I try to add a new control in the main form as well as in the visual editor, i get the following error:
child is not a child control of this parent
So my question is: is there a way to work around this error, and move the controls from the usercontrol to the content panel?
Note that I do want this to be automated in the ThemedForm class, instead of calling methods from the main form.
EDIT:
I tried this:
http://forums.asp.net/t/617980.aspx
But that will only cause visual studio to freeze, and then I need to restart.
I know that it is not really appropriate to answer ones own question, however the solution I came up with will take quite some explaining, which will be too much to add in my question with an edit.
So here we go:
Inside the inherited 'ThemedForm' class, I created a private variable, in order to be able to return the variable when the Controls property would be called:
private Controls controls = null;
I set the variable to null, because I need to pass variables to the class in the 'ThemedForm' class constructor. I will create a new instance of the class later on.
Then I created a class to replace the Controls property:
public class Controls
{
private Control contentPanel = null;
private ThemedForm themedform = null;
public Controls(ThemedForm form, Control panel)
{
contentPanel = panel;
themedform = form;
}
public void Add(Control control)
{
if (control != contentPanel)
{
contentPanel.Controls.Add(control);
}
else
{
themedform.Controls_Add(control);
}
}
public void Remove(Control control)
{
if (control != contentPanel)
{
contentPanel.Controls.Remove(control);
}
else
{
themedform.Controls_Remove(control);
}
}
}
I know this class holds far from all functionality of the original Controls property, but for now this will have to do, and if you like, you can add your own functionality.
As you can see in the Add and Remove methods in the Controls class, I try to determine wether the control that needs to be added is either the content panel I want to add the rest of the controls to, or any other control that needs to be added to the content panel.
If the control actually is the content panel, I add or remove it to or from the Controls property of the base class of the 'ThemedForm' class, which is a 'Form' class. Otherwise, I just add the control to the content panel's Controls property.
Then I added the Controls_Add and Controls_Remove methods to the 'ThemedForm' class, in order to be able to add or remove a control from the Controls property of the 'ThemedForm' base class.
public void Controls_Add(Control control)
{
base.Controls.Add(control);
}
public void Controls_Remove(Control control)
{
base.Controls.Remove(control);
}
They are quite self-explanatory.
In order to call the Controls.Add or the Controls.Remove methods from an external class, I needed to add a public property that hid the current Controls property, and returned the private variable that I assigned to the replacing class.
new public Controls Controls
{
get { return controls; }
}
And finally I created a new instance of the Controls class, passing the current 'ThemedForm' class, and the contentPanel control, in order to get it all to run.
_controls = new Controls(this, contentPanel);
After doing all this, I was able to 'redirect' any controls that were added to the UserControl (even inside the visual editor) to the content panel. This allowed me to use the Dock property of any control, and it would dock inside the content panel, instead of over my entire form.
This is still a little bit buggy, because inside the visual editor the docked controls still seem like they are docked over the entire form, but when running the application the result is as I wanted.
I really hope this helps anyone.
I am having a peculiar problem with the order in which TextBox controls are added in to the form's Controls property.
Currently, I have the function:
public static bool IsValidate(System.Windows.Forms.Form Frm)
{
foreach (Control ctrl in Frm.Controls)
if (ctrl is TextBox)
// if (((TextBox)ctrl).AccessibleDescription == "Valid" && ((TextBox)ctrl).Text == string.Empty)
if (((TextBox)ctrl).AccessibleDescription == "Valid" && ((TextBox)ctrl).Text.Trim()== "")
{
MessageBox.Show(((TextBox)ctrl).AccessibleName + " Can't be Blank", Program.companyName, MessageBoxButtons.OK, MessageBoxIcon.Stop);
((TextBox)ctrl).Focus();
return false;
}
return true;
}
But it's iterating through the textboxes randomly, even though I have set their tab indices.
So I develop the same form again and create the textboxes sequentially. But still, when I pass the form to this function, it's iterating through the textboxes randomly.
I want to know if there is any property of the controls that would allow me to manage their flow.
You can do its easily.
Please use following syntax and which sort controls as per your tabindex in your form
foreach (Control control in this.Controls.Cast<Control>()
.OrderBy(c => c.TabIndex))
{
}
It's much easier to sort controls manually than manage their order in Controls collection. Example (sorts by TabOrder):
private static int CompareTabIndex(TextBox c1, TextBox c2)
{
return c1.TabIndex.CompareTo(c2.TabIndex);
}
public static bool IsValid(Form form)
{
List<TextBox> textBoxes = new List<TextBox>();
foreach(Control ctl in form.Controls)
{
TextBox textBox = ctl as TextBox;
if(textBox != null) textBoxes.Add(textBox);
}
textBoxes.Sort(new Comparison<TextBox>(CompareTabIndex));
foreach(TextBox textBox in textBoxes)
{
if(textBox.AccessibleDescription == "Valid" && textBox.Text.Trim() == "")
{
MessageBox.Show(textBox.AccessibleName + " Can't be Blank",
Program.companyName, MessageBoxButtons.OK, MessageBoxIcon.Stop);
textBox.Focus();
return false;
}
}
return true;
}
Is it really iterating over the controls "randomly"? (Implying that it is non-deterministic and the order is likely to change each time.) Or is it iterating over the controls in the same order each time, but not the order you expect? I suspect it's the latter, given that the C# language specification explicitly states the ordering of foreach (see first answer).
The tab order certainly won't affect the ordering of the controls. That's just for UI purposes. The actual order of the controls as array elements in the backing store is more likely controlled by the order in which they were created when building the form.
Can you elaborate more on that last part where you develop the form again "and take the text box sequentially"?
The controls are placed in order of the Z-order of the controls in the same parent container (top-most to bottom-most). To test try placing controls on a form and get the order. Apply "Send to Back" or "Bring to Front" for a few controls (at design time or runtime). The order of the foreach will change with the topmost control first and downwards.
The generated Designer code adds the controls based on the z-order. Lowest control first (top most control last). Hence it seems like it is based on the order in which it is added to the container.
I'm not sure if the implementation of BringToFront() and SendToBack() internally removes and adds controls in the required order. To me it makes sense to have it based on the z-order. And like mentioned above, we can always use our own ordering if required.
You can drop the controls into the form in the designer visually, and then open up the Form.Designer.cs source file and locate where the designer has typed in the code to add the controls to the Controls collection (i.e. the Controls.Add lines) and re-order those lines in the *.Designer.cs by hand. Once you've done that, the designer should leave your changes alone. I noticed that the designer writes them in reverse order. Your foreach should find them in the order that you arranged them.
i had this problem and i changed the order control name on Designer.cs ,
this.groupBox3.Controls.Add(this.txtPrice);
this.groupBox3.Controls.Add(this.txtDate);
See the Document Outline of the Form in view -> Other Windows -> Document Outline.
And then change the hierarchy of the controls as you need. the foreach looks there