Rendering Nested Server Controls - c#

This should be relatively easy. I do not know why I am struggling with it, but Google cannot help me find an example of what I want to do.
I am building a navigation/menu out a simple nested structure of ULs with LIs. The parent object that is placed on the page is called NavController. It has a collection of NavItems.
So I pretty much let the NavController handle building the base UI, creating the CSS, and JavaScript. It also creates the base UL for the top most level of LIs. From my research and understanding the UI creation should be done in the CreateChildControls method.
Here is my code:
protected override void CreateChildControls()
{
Controls.Clear();
this.Page.RegisterClientJavaScript("~/Resources/Script/SideNav.js");
_ClientObject = this.GetClientJavaScriptObject("SideNav", this.ClientID);
Controls.Add(_BaseContainer);
HtmlGenericControl innerContents = this.BuildBaseContainer();
this.BuildList(innerContents);
_ClientObject.AddParameter("BaseContainerID", _BaseContainer.ClientID, true);
_ClientObject.AddParameter("ImgId", _SideBarTabImg.ClientID, true);
_ClientObject.AddParameter("SideBarContentsId", _SideBarContents.ClientID, true);
base.CreateChildControls();
}
The BuildList method actually builds the top level LIs. I am unsure how this method is called, but I can see that it is. Inside the BuildList function I add to the innerContents's Controls collection the NavItem objects.
First off, is this correct? Placing another custom server control inside of of a UL (with the caveat that the custom control is basically rendering an LI; more on this later)?
The CreateChildControls is never being called for the NavItem. After looking around on the Internet it seemed like it was because I was never calling EnsureChildControls. This confuses me because It will only fire when the ChildControlsCreated property is false. Once it is run this property becomes true. If I have a Text and Href on the NavItem and EnsureChildControls is called when either of them are set then how will the other's value be placed into any sub control?
Just to clog up some screen space here is the code that I have so far for the NavItem:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
namespace AthletesCafe.Web.WebControls.SideNav
{
[ToolboxData("<{0}:NavItem runat=server></{0}:NavItem>")]
public class NavItem : ExtendedWebControl
{
private string _Text;
private string _Value;
private IList<NavItem> _ChildItems = new List<NavItem>();
private HtmlGenericControl _ListItem = new HtmlGenericControl("LI");
private HtmlAnchor _Link = new HtmlAnchor();
private HtmlGenericControl _SubList = new HtmlGenericControl("UL");
public string Text
{
get
{
EnsureChildControls();
return _Text;
}
set
{
EnsureChildControls();
_Text = value;
_Link.InnerText = _Text;
}
}
public string Value
{
get
{
EnsureChildControls();
return _Value;
}
set
{
EnsureChildControls();
_Value = value;
_Link.HRef = _Value;
}
}
protected override void CreateChildControls()
{
Controls.Clear();
Controls.Add(_ListItem);
_ListItem.Controls.Add(_Link);
base.CreateChildControls();
}
public NavItem AddChildItem(string text, string url)
{
EnsureChildControls();
NavItem newItem = new NavItem();
newItem.Text = text;
newItem.Value = url;
_ChildItems.Add(newItem);
this.BuildSubList();
return newItem;
}
private void BuildSubList()
{
_SubList.Controls.Clear();
if (_ChildItems.Count > 0)
{
if (!_ListItem.Controls.Contains(_SubList))
_ListItem.Controls.Add(_SubList);
foreach (NavItem item in _ChildItems)
_SubList.Controls.Add(item);
}
}
protected override void Render(HtmlTextWriter writer)
{
RenderContents(writer);
}
protected override void RenderContents(HtmlTextWriter writer)
{
_ListItem.RenderControl(writer);
//base.RenderContents(writer);
}
public override string ToString()
{
EnsureChildControls();
return _Text + " - " + _Value;
}
}
}
I don't understand why I can't just set the value and text in their public properties and then use some method or manner to place it into the controls before render.
Please, please some point me to the correct way to do this. I have a bunch of these little controls that I need to make that basically do this same approach. I'm going to start a rudimentary toolbar soon and this information would help!
EDIT - Placing in code that shows the list building for clarification:
HtmlGenericControl list = new HtmlGenericControl("UL");
innerContents.Controls.Add(list);
foreach (NavItem item in _Items)
{
list.Controls.Add(item);
}

Once you override CreateChildControls(), you basically become responsible for making sure that your nested controls get created properly.
Your approach seems pretty standard, however, for your simple properties, I do not think you need to be calling EnsureChildControls(). If you picked this up from the MSDN docs on the function, take another look - the "Text" property example that they give is retrieving the text value from one of its child controls, which means that the property accessor needs to make sure the child controls exist.
You cannot guarantee when someone might access your control, which is why you would put in EnsureChildControl() calls before any access to a control in your Controls collection. In a normal page lifecycle, CreateChildControls() will get called when ASP.net gets around to adding your composite control to the page. This may not happen until PreRender on the first page load if nobody touches your control and it doesn't databind or anything.
On subsequent postbacks, CreateChildControls will be called during ViewState processing, or any code might touch off a composite control to start creating it's child controls. FindControl() will do it automatically, for example - and this could happen in OnInit().
Hope that helps. I think you're on the right track.

Related

Custom ComboBox: prevent designer from adding to Items

I have a custom combobox control that is supposed to show a list of webcams available.
The code is fairly tiny.
using System;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using DirectShowLib;
namespace CameraSelectionCB
{
public partial class CameraComboBox : ComboBox
{
protected BindingList<string> Names;
protected DsDevice[] Devices;
public CameraComboBox()
{
InitializeComponent();
Devices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
Names = new BindingList<string>(Devices.Select(d => d.Name).ToList());
this.DataSource = Names;
this.DropDownStyle = ComboBoxStyle.DropDownList;
}
}
}
However, I ran into a couple of bugs.
First, whenever I place an instance of this combobox, designer generates the following code:
this.cameraComboBox1.DataSource = ((object)(resources.GetObject("cameraComboBox1.DataSource")));
this.cameraComboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cameraComboBox1.Items.AddRange(new object[] {
"HP Webcam"});
Which results in an exception during runtime, since Items should not be modified when DataSource is set. This happens even if I don't touch Items property in the designer.
"HP Webcam" is the only camera present on my computer at the time.
How can I suppress this behaviour?
When you drop your control on a form the constructor code and any loading code will run. Any code in there that changes a property value will be executed in designtime and therefore will be written in the designer.cs of the form you dropped your control on.
When programming controls you should always keep that in mind.
I solved this by adding a property that I can use to check if the code executed in designtime or runtime.
protected bool IsInDesignMode
{
get { return DesignMode || LicenseManager.UsageMode == LicenseUsageMode.Designtime; }
}
protected BindingList<string> Names;
protected DsDevice[] Devices;
public CameraComboBox()
{
InitializeComponent();
if (InDesignMode == false)
{
// only do this at runtime, never at designtime...
Devices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
Names = new BindingList<string>(Devices.Select(d => d.Name).ToList());
this.DataSource = Names;
}
this.DropDownStyle = ComboBoxStyle.DropDownList;
}
Now the binding will only happen at runtime
Dont forget to remove the generated code in the Designer.cs file when you try this
The problem is that the binding in constructor is being run by the designer. You could try moving it to the Initialise or Loaded events

Responsive Design UI with DockPanel Suite

I have design 1 winform to look like the picture. But I want the highlighted yellow part to be dockable with dockpanel suite reference. Is that do-able or any other suggestion of better design?
Right now the treeview is on the dockpanel and the red box part is a usercontrol placed in the same dockpanel. I tried to put the redbox as another form but I can't place it as it is in the picture. Also, this winform is need to be responsive so I put in the redbox part in a table layout panel.winform design and not familiar actually with the dockpanel suite reference. If there is a beginner tutorial that I can refer to, it would be much appreciated.
Current design:
There are two approach to your problem. First is dirty one and second elegant one. By dirty and elegant i mean way they display. Method they work are both same.
I will explain to you how to do it on empty form and you just implement that in your populated one.
First create new form.
Add 2 or more GroupBoxes to it
Add some items inside them (just to see if it works)
At the top of the each boxes add Button which will toggle visibility
Our form now looks like this and let's look of code behind it.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Test
{
public partial class TestForm : Form
{
// This is property
bool ShowFirstGroupBox
{
get
{
// We let user get our property from private variable
return _ShowFirstGroupBox;
}
set
{
// When user change this property we do something based on that
switch(value)
{
case true:
groupBox1.Size = new Size(groupBox1.Width, FirstGroupBoxDefaultHeight);
break;
case false:
groupBox1.Size = new Size(groupBox1.Width, 55);
break;
}
_ShowFirstGroupBox = value;
}
}
bool ShowSecondGroupBox
{
get
{
return _ShowSecondGroupBox;
}
set
{
switch (value)
{
case true:
groupBox2.Size = new Size(groupBox1.Width, FirstGroupBoxDefaultHeight);
break;
case false:
groupBox2.Size = new Size(groupBox1.Width, 55);
break;
}
_ShowSecondGroupBox = value;
}
}
// We store our boxes current state ( TRUE = shown, FALSE = HIDDEN )
bool _ShowFirstGroupBox = true;
bool _ShowSecondGroupBox = true;
// We store our default height for groupboxes
int FirstGroupBoxDefaultHeight;
int SecondGroupBoxDefaultHeight;
public TestForm()
{
InitializeComponent();
// Assigning default height of our groupboxes
FirstGroupBoxDefaultHeight = groupBox1.Height;
SecondGroupBoxDefaultHeight = groupBox2.Height;
}
private void button1_Click_1(object sender, EventArgs e)
{
ShowFirstGroupBox = !(_ShowFirstGroupBox); // This sets our property value to opposite of this boolean
}
private void button1_Click_1(object sender, EventArgs e)
{
ShowSecondGroupBox = !(_ShowSecondGroupBox); // This sets our property value to opposite of this boolean
}
}
}
Now when we have code like this and press button it will collapse groupbox.
NOTE: Controls under groupbox are still on place but just hidden since they are child of groupbox and everything outside of bounds is not visible to user.
This is dirty way since i would like to display it much prettier with MINUS sign on the right side of the groupbox title so i do not have button inside it. To do this you would need to create custom control which inherits groupbox, add button to it and position it in title bar and create event for it. It is easy if you have ever tried creating custom controls but if you haven't and you think dirty approach is okay with you then do not try it.

Copying a TabItem with an MVVM structure

This is an attempt to expand on this question. In my WPF program I've been cloning tabItems by using an XamlWriter in a function called TrycloneElement. I originally found this function here, but the function can also be viewed in the link to my previous question.
Now that I am beginning to worry about functionality inside my program, I found that the TrycloneElement function does not replicate any code-behind functionality assigned to the tabItem that it is cloning.
Because of High Core's link and comment on my earlier question I decided to start implementing functionality on my tabItems through Data Binding with my ViewModel.
Here is a sample of a command that I've implemented:
public viewModel()
{
allowReversing = new Command(allowReversing_Operations);
}
public Command AllowReversing
{
get { return allowReversing; }
}
private Command allowReversing;
private void allowReversing_Operations()
{
//Query for Window1
var mainWindow = Application.Current.Windows
.Cast<Window1>()
.FirstOrDefault(window => window is Window1) as Window1;
if (mainWindow.checkBox1.IsChecked == true) //Checked
{
mainWindow.checkBox9.IsEnabled = true;
mainWindow.groupBox7.IsEnabled = true;
}
else //UnChecked
{
mainWindow.checkBox9.IsEnabled = false;
mainWindow.checkBox9.IsChecked = false;
mainWindow.groupBox7.IsEnabled = false;
}
}
*NOTE: I know that I cheated and interacted directly with my View in the above code, but I wasn't sure how else to run those commands. If it is a problem, or there is another way, please show me how I can run those same commands without interacting with the View like I did.
Now to the question:
After changing my code and adding the commands to my ViewModel, the TrycloneElement function no longer works. At run time during the tab clone I receive an XamlParseException on line, object x = XamlReader.Load(xmlReader); that reads:
I'm fine with ditching the function if there is a better way and I don't need it anymore. But ultimately, how do I take a tabItem's design and functionality and clone it? (Please keep in mind that I really am trying to correct my structure)
Thank you for your help.
Revision of Leo's answer
This is the current version of Leo's answer that I have compiling. (There were some syntax errors)
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
Here is my example of a properly-implemented dynamic TabControl in WPF.
The main idea is that each Tab Item is a separate widget that contains its own logic and data, which is handled by the ViewModel, while the UI does what the UI must do: show data, not contain data.
The bottom line is that all data and functionality is managed at the ViewModel / Model levels, and since the TabControl is bound to an ObservableCollection, you simply add another element to that Collection whenever you need to add a new Tab.
This removes the need for "cloning" the UI or do any other weird manipulations with it.
1.) To fix that XamlParseException, make sure you have a public constructor like an empty one, you probably defined a constructor and when you tried to serialize that object and deserialize it can't. You have to explicitly add the default constructor.
2.) I don't like the word clone, but I'd say, when they want to copy. I'll manually create a new tab item control then do reflection on it.
I have this code that I made
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] {new PropertyFilterAttribute(PropertyFilterOptions.SetValues)})
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd where dpd != null select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly))
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
So it would be like
var newTabItem = new TabItem();
newTabItem.CopyPropertiesFrom(masterTab);

WP7 Auto Grow ListBox upon reaching the last item

I'm trying to achieve an effect where more items are appended to the list when the user scrolls down to the last item. I haven't found a way to determine if the user has scrolled to the end of the list. I don't see a event on ListBox that is fired when the user reaches the bottom of the list. Something that tells me when an item has been scrolled into view would be great, but as far as I can tell, there is nothing like that.
Is this even possible in WP7?
Edit: Another way of saying this is, can we detect when a list has "bounced"?
Daniel Vaughan has posted an example of how to detect for this at http://danielvaughan.orpius.com/post/Scroll-Based-Data-Loading-in-Windows-Phone-7.aspx
It isn't super easy to get going since there are a lot of moving parts, but here is what you can do, assuming you want a short list that loads more from your data as you get scrolling down, similar to a lot of twitter apps, etc.
Write your own subclass of ObservableCollection that only offers up a few items (like 20), keeping the rest held back until requested
Hook up to the scroll viewer (inside the listbox or container) and its visual state changed events, you can get the NotScrolling and Scrolling changes; for an example see this code by ptorr
When scrolling stops, use viewer scroll extensions code to see where things are extended (at the bottom or not) or just the raw scroll viewer properties to see if it is extended to the bottom
If so, trigger your observable collection to release another set of items.
Sorry I don't have a complete sample ready to blog yet. Good luck!
I've just implemented this for Overflow7.
The approach I took was similar to http://blog.slimcode.com/2010/09/11/detect-when-a-listbox-scrolls-to-its-end-wp7/
However, instead of using a Style I did the hook up in code.
Basically derived my parent UserControl from:
public class BaseExtendedListUserControl : UserControl
{
DependencyProperty ListVerticalOffsetProperty = DependencyProperty.Register(
"ListVerticalOffset",
typeof(double),
typeof(BaseExtendedListUserControl),
new PropertyMetadata(new PropertyChangedCallback(OnListVerticalOffsetChanged)));
private ScrollViewer _listScrollViewer;
protected void EnsureBoundToScrollViewer()
{
if (_listScrollViewer != null)
return;
var elements = VisualTreeHelper.FindElementsInHostCoordinates(new Rect(0,0,this.Width, this.Height), this);
_listScrollViewer = elements.Where(x => x is ScrollViewer).FirstOrDefault() as ScrollViewer;
if (_listScrollViewer == null)
return;
Binding binding = new Binding();
binding.Source = _listScrollViewer;
binding.Path = new PropertyPath("VerticalOffset");
binding.Mode = BindingMode.OneWay;
this.SetBinding(ListVerticalOffsetProperty, binding);
}
public double ListVerticalOffset
{
get { return (double)this.GetValue(ListVerticalOffsetProperty); }
set { this.SetValue(ListVerticalOffsetProperty, value); }
}
private static void OnListVerticalOffsetChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
BaseExtendedListUserControl control = obj as BaseExtendedListUserControl;
control.OnListVerticalOffsetChanged();
}
private void OnListVerticalOffsetChanged()
{
OnListVerticalOffsetChanged(_listScrollViewer);
}
protected virtual void OnListVerticalOffsetChanged(ScrollViewer s)
{
// do nothing
}
}
this then meant that in the user control itself I could just use:
protected override void OnListVerticalOffsetChanged(ScrollViewer viewer)
{
// Trigger when at the end of the viewport
if (viewer.VerticalOffset >= viewer.ScrollableHeight)
{
if (MoreClick != null)
{
MoreClick(this, new RoutedEventArgs());
}
}
}
private void ListBox1_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
EnsureBoundToScrollViewer();
}
The "hacky" thing here was that I had to use ListBox1_ManipulationCompleted and VisualTreeHelper to find my ScrollViewer - I'm sure there are better ways...
Have a look at this detect Listbox compression state from msdn blog
Use the DeferredLoadListBox.

Persisting User Input for Dynamic Control in SharePoint Web Part

Edit at bottom with solution
I've seen a similar question to this posted before and have tried the suggestions, but I must be missing something. My basic problem is this: I have a select box where the user can select a filter which may or may not have constraints built into it (requires the user to input further data so the filter knows how to filter). Since it's unknown just how many constraints will exist for the filter, I'm trying to load them in dynamically and add them to a placeholder panel that I have. The correct number of constraints load just fine and dandy, but when the user inputs text and hits submit, after the page reloads none of the values persist. Here's the appropriate code (I can provide more if needed):
I have these as class variables for my Web Part:
Panel constraintPanel;
HtmlInputText[] constraints;
Label[] constraintLabels = null;
Inside an override CreateChildControls I initialize the panel:
constraintPanel = new Panel();
I build in the dynamic input boxes in an overridden OnPreRender (Note: I've heard people say to do it in OnInit, OnLoad, or OnPreRender, but OnPreRender is the only one that doesn't make the whole Web Part error out):
protected override void OnPreRender(EventArgs e)
{
buildConstraints();
base.OnPreRender(e);
}
private void buildConstraints()
{
if (!viewSelect.SelectedValue.Equals(INConstants.NoFilterOption))
{
string[,] constraintList = docManager.GetFilterConstraints(viewFilterSelect.SelectedValue);
if (constraintList != null)
{
this.constraints = new HtmlInputText[constraintList.Length / 2];
this.constraintLabels = new Label[constraintList.Length / 2];
for (int constraintCount = 0; constraintCount < constraintList.Length / 2; constraintCount++)
{
Label constraintLabel = new Label();
constraintPanel.Controls.Add(constraintLabel);
constraintLabel.Text = constraintList[constraintCount, 0];
this.constraintLabels[constraintCount] = constraintLabel;
HtmlInputText constraint = new HtmlInputText();
constraintPanel.Controls.Add(constraint);
constraint.ID = "constraint_" + constraintCount;
constraint.MaxLength = 12;
constraint.Style.Add("FONT-FAMILY", "Verdana");
constraint.Style.Add("FONT-SIZE", "11");
this.constraints[constraintCount] = constraint;
}
}
}
}
And then finally inside an overridden RenderWebParts I have (note: I've also tried looping through the arrays constraints and constraintLabels to render the controls, but it made no difference):
...
constraintPanel.RenderBeginTag(output); // not sure if this is needed
if (constraints != null && constraints.Length > 0)
{
foreach (Control tempControl in constraintPanel.Controls)
{
if (tempControl is Label)
{
output.WriteLine("<tr>");
output.WriteLine("<td width='2%' nowrap><font class='search-header'>");
tempControl.RenderControl(output);
output.WriteLine(" ");
}
else if (tempControl is HtmlInputText)
{
tempControl.RenderControl(output);
output.WriteLine("</td>");
output.WriteLine("<td width='*' nowrap></td>");
output.WriteLine("</tr>");
}
}
}
constraintPanel.RenderEndTag(output); // not sure if this is needed
...
I appreciate any help, as this is truly driving me crazy.
Edit with solution:
I've been able to get it working. I needed to override the OnLoad event and wrap my calls from there in a try-catch block. For some reason the initial page load throws an exception when trying to run, which causes the entire page to not display. I also forgot to add my constraintPanel to the Controls list.
Here's the code in OnLoad for information's sake:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
try
{
viewsBuildConstraints();
}
catch (Exception)
{
}
}
Try marking your webpart with the INamingContainer interface and make sure to give all controls an ID. Furthermore, HtmlInput COntrols do not have a viewstate i believe, which would cause them to "forget" the input after a postback. Could you try using actual TextBox controls?

Categories