VirtualizingStackPanel with cache - c#

We have to requirement to virtualize a ListView/ItemsControl with a VirtualizingStackPanel. Although everything works as expected, the Control's ItemTemplate adheres a complex control with a lot of computation during its initialization phase - which has to be done on the UI thread. In other words, scrolling leads to UI freezes - which is fine if it only has to be done once. As we can't use the VirtualizingStackPanel.VirtualizationMode="Recycle" (due to several other restrictions) we have to try something different.
I thought of a "cached" virtualizingStackPanel which doesn't actually dispose the ItemTemplate's Template, but rather 'freezes' the control. When the user scrolls back to a - previously loaded template - we could simply 'unfreeze' the control.
The 'freeze' can be implemented by overwriting OnCleanUpVirtualizedItem, such as:
protected override void OnCleanUpVirtualizedItem(CleanUpVirtualizedItemEventArgs args)
{
var stuff = FindChild<HeavyStuff>(args.UIElement);
if (stuff != null)
{
int idx = Children.IndexOf(args.UIElement);
if (!_buffer.ContainsKey(idx))
_buffer.Add(idx, args.UIElement);
stuff.Freeze();
args.Handled = true;
args.Cancel = true;
}
else
{
base.OnCleanUpVirtualizedItem(args);
}
}
That works pretty well. The control stays within the VisualTree and it simply 'freezes' and avoids any user-input and the potential resulting workload. However, I couldn't figure out on howto 'unfreeze' the control when it comes back into view. I dug through the reference-source and found the BringIndexIntoView, which could potentially solve my issue like the following:
protected override void BringIndexIntoView(int index)
{
if (_buffer.ContainsKey(index))
{
FindChild<HeavyStuff>(_buffer[index]).UnFreeze();
}
else
{
base.BringIndexIntoView(index);
}
}
However, that method never gets called by the internal VirtualizingStackPanel logic. My second thought was to override the IItemContainerGenerator, as the generator does provide the DependencyObjects on demand. But again without any luck. One can't inherit the ItemContainerGenerator, because it is sealed. Secondly, defining a proxy and overwriting the ItemContainerGenerator properties doesn't help either, as the base class doesn't call that VirtualizingStackPanel's ItemContainerGenerator property at all:
public new IItemContainerGenerator ItemContainerGenerator => generator;
Is there any way to obtain the information when a control scrolls back into the view, without the VirtualizingStackPanel re-creating an instance?
Addon: I also thought about virtualizing the data-source itself. However, even if we would virtualize the data source, the global user input would lead the controls to perform CPU and UI-thread intensive operations. Hence, it doesn't matter which way we choose, we do have to 'freeze' and 'unfreeze' certain, non-viewport-related controls. In other words, we need UI virtualization nevertheless.
EDIT: "Freeze" and "Unfreeze" does not refer to the .NET object freezing. My poor choice of words may cause that confusion. With "freeze" and "unfreeze" I do refer to some internal logic which subscribes or unsubscribes from various event handlers, such that controls, beeing out of the viewport, don't require to process that input.

You can use the following example implementation that extends the StackPanel to tracks the visibility of its hosted containers (in terms of the parent scroll viewer's viewport).
Simply set the custom Panel as ItemsPanel to the ListBox.
It's important that the parent ScrollViewer has the CanContentScroll property set to true (which is the default for the ListBox).
Since StackPanel already implements IScrollInfo, observing the scroll event and viewport is very straight forward.
Add your actual implementation, to handle the changed containers and/or their hosted models, to the OnHiddenContainersChanged method to complete the Panel.
public class ScrollWatcherPanel : StackPanel
{
public ScrollWatcherPanel()
{
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (!this.ScrollOwner.CanContentScroll)
{
throw new InvalidOperationException("ScrollViewer.CanContentScroll must be enabled.");
}
this.ScrollOwner.ScrollChanged += OnScrollChanged;
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
HandleAllContainers();
}
private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
=> HandleContainerVisibilityChanges((int)e.VerticalChange);
private void HandleAllContainers()
{
int containersBeforeViewportStartCount = (int)this.VerticalOffset;
int containersBeforeViewportEndCount = containersBeforeViewportStartCount + (int)this.ViewportHeight + 1;
var newHiddenContainers = new List<FrameworkElement>();
var newVisibleContainers = new List<FrameworkElement>();
for (int childContainerIndex = 0; childContainerIndex < this.InternalChildren.Count; childContainerIndex++)
{
bool isContainerHiddenBeforeViewport = childContainerIndex < containersBeforeViewportStartCount;
bool isContainerVisibleInViewport = childContainerIndex < containersBeforeViewportEndCount;
var childContainer = (FrameworkElement)this.InternalChildren[childContainerIndex];
if (isContainerHiddenBeforeViewport)
{
newHiddenContainers.Add(childContainer);
}
else if (isContainerVisibleInViewport)
{
newVisibleContainers.Add(childContainer);
}
else // Container is hidden after viewport
{
newHiddenContainers.Add(childContainer);
}
}
OnHiddenContainersChanged(newHiddenContainers, newVisibleContainers);
}
private void HandleContainerVisibilityChanges(int verticalChange)
{
int containersBeforeViewportStartCount = (int)this.VerticalOffset;
int containersBeforeViewportEndCount = containersBeforeViewportStartCount + (int)this.ViewportHeight + 1;
int newHiddenContainerCount = Math.Abs(verticalChange);
int newVisibleContainerCount = Math.Abs(verticalChange);
bool isScrollingDown = verticalChange > 0;
int changeCount = Math.Abs(verticalChange);
var newHiddenContainers = new List<FrameworkElement>();
var newVisibleContainers = new List<FrameworkElement>();
int changesIndex = Math.Max(0, containersBeforeViewportStartCount - changeCount);
for (int childContainerIndex = changesIndex; childContainerIndex < this.InternalChildren.Count; childContainerIndex++)
{
bool isContainerHiddenBeforeViewport = childContainerIndex < containersBeforeViewportStartCount;
bool isContainerVisibleInViewport = childContainerIndex < containersBeforeViewportEndCount;
var childContainer = (FrameworkElement)this.InternalChildren[childContainerIndex];
if (isContainerHiddenBeforeViewport)
{
if (isScrollingDown)
{
bool isContainerNewHidden = childContainerIndex >= containersBeforeViewportStartCount - changeCount
&& newHiddenContainerCount > 0;
if (isContainerNewHidden)
{
newHiddenContainers.Add(childContainer);
newHiddenContainerCount--;
}
}
}
else if (isContainerVisibleInViewport)
{
if (isScrollingDown)
{
bool isContainerNewVisible = childContainerIndex >= containersBeforeViewportEndCount - changeCount
&& newVisibleContainerCount > 0;
if (isContainerNewVisible)
{
newVisibleContainers.Add(childContainer);
newVisibleContainerCount--;
}
}
else
{
bool isContainerNewVisible = childContainerIndex >= containersBeforeViewportStartCount
&& newVisibleContainerCount > 0;
if (isContainerNewVisible)
{
newVisibleContainers.Add(childContainer);
newVisibleContainerCount--;
}
}
}
else // Container is hidden after viewport (on scroll up)
{
if (!isScrollingDown)
{
bool isContainerNewHidden = childContainerIndex >= containersBeforeViewportEndCount
&& newHiddenContainerCount > 0;
if (isContainerNewHidden)
{
newHiddenContainers.Add(childContainer);
newHiddenContainerCount--;
if (newHiddenContainerCount == 0)
{
break;
}
}
}
}
}
OnHiddenContainersChanged(newHiddenContainers, newVisibleContainers);
}
protected virtual void OnHiddenContainersChanged(IEnumerable<FrameworkElement> newHiddenContainers, IEnumerable<FrameworkElement> newVisibleContainers)
{
// TODO::Handle "hidden"/"visible" item containers, that are just scrolled out of/into the viewport.
// You can access the DataContext of the containers to get a reference to the underlying data model.
}
}

Related

C# - show child control (Listbox) on top of parent control(textbox) in winform

Created a custom intellisense textbox (textbox with listbox a child).
As shown in below image, the listbox pops up when i enter a char which all works fine and good but when i am at the end of textbox the listbox is partially visible, is there anyway i can show the whole listbox content?
Tried this "Show control inside user control outside the boundaries of its parent
But when the popup window opens the text box looses focus and i cannot type anything further, my intellisense textbox keeps giving better results based on what they type but in this situation i am not able to type anymore.
FYI tried to add pParentControl.Focus() into show method defined in other article as shown below, missing something?
public void Show(Control pParentControl)
{
if (pParentControl == null) return;
// position the popup window
var loc = pParentControl.PointToScreen(new Point(0, pParentControl.Height));
pParentControl.Focus();
m_tsdd.Show(loc);
}
Here is the complete code
class TextBox_AutoComplete : TextBox
{
#region Class Members
List<string> dictionary;
ListBox listbox = new ListBox();
#endregion
private PopupHelper m_popup;
#region Extern functions
[DllImport("user32")]
private extern static int GetCaretPos(out Point p);
#endregion
#region Constructors
public TextBox_AutoComplete() : base()
{
this.Margin = new Padding(0, 0, 0, 0);
this.Multiline = true;
this.Dock = DockStyle.Fill;
this.KeyDown += Textbox_KeyDown;
this.KeyUp += Textbox_KeyUp;
listbox.Parent = this;
listbox.KeyUp += List_OnKeyUp;
listbox.Visible = false;
this.dictionary = new List<string>();
}
#endregion
#region Properties
public List<string> Dictionary
{
get { return this.dictionary; }
set { this.dictionary = value; }
}
#endregion
#region Methods
private static string GetLastString(string s)
{
Regex rgx = new Regex("[^a-zA-Z0-9_.\\[\\]]");
s = rgx.Replace(s, " ");
string[] strArray = s.Split(' ');
return strArray[strArray.Length - 1];
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
Point cp;
GetCaretPos(out cp);
List<string> lstTemp = new List<string>();
List<string> TempFilteredList = new List<string>();
string LastString = GetLastString(this.Text.Substring(0, SelectionStart));
//MessageBox.Show(LastString);
/*seperated them so that column name matches are found first*/
TempFilteredList.AddRange(dictionary.Where(n => n.Replace("[", "").ToUpper().Substring(n.IndexOf(".") > 0 ? n.IndexOf(".") : 0).StartsWith(LastString.ToUpper())
).Select(r => r)
.ToList());
TempFilteredList.AddRange(dictionary.Where(n => n.Replace("[", "").ToUpper().StartsWith(LastString.ToUpper())
|| n.ToUpper().StartsWith(LastString.ToUpper()))
.Select(r => r)
.ToList());
lstTemp = TempFilteredList.Distinct().Select(r => r).ToList();
/*Getting max width*/
int maxWidth = 0, temp = 0;
foreach (var obj in lstTemp)
{
temp = TextRenderer.MeasureText(obj.ToString(), new Font("Arial", 10, FontStyle.Regular)).Width;
if (temp > maxWidth)
{
maxWidth = temp;
}
}
listbox.SetBounds(cp.X + 20, cp.Y + 20, maxWidth, 60);
if (lstTemp.Count != 0 && LastString != "")
{
listbox.DataSource = lstTemp;
// listbox.Show();
if (m_popup == null)
m_popup = new PopupHelper(listbox);
m_popup.Show(this);
}
else if (m_popup != null)
{
//listbox.Hide();
m_popup.Hide();
}
}
protected void Textbox_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Down)
{
if (listbox.Visible == true)
{
listbox.Focus();
}
e.Handled = true;
}
else if (e.KeyCode == Keys.Escape)
{
listbox.Visible = false;
e.Handled = true;
}
}
protected void Textbox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Space && listbox.Visible == true)
{
listbox.Focus();
List_OnKeyUp(listbox, new KeyEventArgs(Keys.Space));
e.Handled = true;
}
if (e.KeyCode == Keys.Down && listbox.Visible == true)
{
listbox.Focus();
e.Handled = true;
}
}
private void List_OnKeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Space || e.KeyCode == Keys.Enter)
{
int Selection_Start = this.SelectionStart;
string StrLS = GetLastString(this.Text.Substring(0, Selection_Start));
this.Select(Selection_Start - StrLS.Length, StrLS.Length);
// MessageBox.Show(this.Selection_Start.ToString() + " Last string" + StrLS);
this.SelectedText=((ListBox)sender).SelectedItem.ToString();
listbox.Hide();
this.Focus();
}
}
#endregion
}
public sealed class PopupHelper : IDisposable
{
private readonly Control m_control;
private readonly ToolStripDropDown m_tsdd;
private readonly Panel m_hostPanel; // workarround - some controls don't display correctly if they are hosted directly in ToolStripControlHost
public PopupHelper(Control pControl)
{
m_hostPanel = new Panel();
m_hostPanel.Padding = Padding.Empty;
m_hostPanel.Margin = Padding.Empty;
m_hostPanel.TabStop = false;
m_hostPanel.BorderStyle = BorderStyle.None;
m_hostPanel.BackColor = Color.Transparent;
m_tsdd = new ToolStripDropDown();
m_tsdd.CausesValidation = false;
m_tsdd.Padding = Padding.Empty;
m_tsdd.Margin = Padding.Empty;
m_tsdd.Opacity = 0.9;
m_control = pControl;
m_control.CausesValidation = false;
m_control.Resize += MControlResize;
//m_hostPanel.Controls.Add(m_control);
m_tsdd.Padding = Padding.Empty;
m_tsdd.Margin = Padding.Empty;
m_tsdd.MinimumSize = m_tsdd.MaximumSize = m_tsdd.Size = pControl.Size;
m_tsdd.Items.Add(new ToolStripControlHost(m_control));
}
private void ResizeWindow()
{
m_tsdd.MinimumSize = m_tsdd.MaximumSize = m_tsdd.Size = m_control.Size;
m_hostPanel.MinimumSize = m_hostPanel.MaximumSize = m_hostPanel.Size = m_control.Size;
}
private void MControlResize(object sender, EventArgs e)
{
ResizeWindow();
}
/// <summary>
/// Display the popup and keep the focus
/// </summary>
/// <param name="pParentControl"></param>
public void Show(Control pParentControl)
{
if (pParentControl == null) return;
// position the popup window
var loc = pParentControl.PointToScreen(new Point(0, pParentControl.Height));
pParentControl.Focus();
m_tsdd.Show(loc);
}
public void Hide()
{
m_tsdd.Hide();
}
public void Close()
{
m_tsdd.Close();
}
public void Dispose()
{
m_control.Resize -= MControlResize;
m_tsdd.Dispose();
m_hostPanel.Dispose();
}
}
Firstly, I personally don't see any benefit in having a control inside another. Yes, the child control is locked inside its parent's boundaries automatically for you, but this benefit is negated by the issue that you're facing, and solving that issue requires the same work as when the two controls have no relation. In both cases, you'll have to do the calculations manually to keep the child visible inside its parent. In the second case the parent is the app's window.
Secondly, I don't recommend using hacks like the one mentioned in the comments to show the child outside its parent's boundaries. The hack creates more issues than it solves, as you found out. And what's the point of that hack anyway? If you want to show the child outside the parent, then don't make it a child control in the first place, and you don't need any hack.
The best solution is the one that you find in any well designed app, and in Windows itself. Open any app, let's say Notepad, and right-click near the upper-left corner. You'll see the context menu pulling to lower-right direction. Now right-click near the other three corners and you'll see the context menu pulling in different direction each time, so it will always be visible inside the app. Now if you resize the app window too small and right-click, the context menu will choose the best direction but some of it will be outside the app because the window is too small. That's why you need your list not to be a child, but it's up to you, and it's only about these edge cases. The solution will be similar in both cases.
You're displaying the list in this line:
listbox.SetBounds(cp.X + 20, cp.Y + 20, maxWidth, 60);
The key is cp.X and cp.Y. This is what decides where the list will appear. You need to make this point dynamic and responsive to the boundaries of the parent. You fixed the width to maxWidth and height to 60, so I will use those values in the calculation.
To make sure the list will not go beyond the bottom:
var y = this.Height < cp.Y + 60 ? this.Height - 60 : cp.Y;
To make sure the list will not go beyond the right:
var x = this.Width < cp.X + maxWidth ? this.Width - maxWidth : cp.X;
Now you can show your list at the calculated point:
listbox.SetBounds(x, y, maxWidth, 60);
Notes:
I didn't include the 20 gap that you used. I think it looks better without the gap and I haven't seen any app that has a gap. If you prefer the gap, add it to the calculation of x and y. Don't add it in the SetBounds() or that will screw up the calculation.
The calculation above doesn't take into account when the parent size is too small to show the child inside. If you want to support that edge case, you need to make the child a separate control and add some checks to the calculation.

Dynamic Data Display LineGraph not updating

I am trying to use Dynamic Data Display for my senior design project, but I'm having difficulty.
I can create the graph just fine, but I can't the line to update at all unless I am zooming in and out.
In most examples, all they had to do was create the graph, then when they manipulate the data, the graph updates itself.
So here are the main functions I am using with regards to the charts:
private List<WindDAQ.WindDataPoint> Chart1Data;
private EnumerableDataSource<WindDAQ.WindDataPoint> Chart1DataSource;
private void InitializeCharts()
{
Chart1Data = new List<WindDAQ.WindDataPoint>();
Chart1DataSource = new EnumerableDataSource<WindDAQ.WindDataPoint>(Chart1Data);
Chart1DataSource.SetXMapping(x => Chart1XAxis.ConvertToDouble(x.Time));
Chart1DataSource.SetYMapping(y => Chart1XAxis.ConvertToDouble(y.Lift));
Chart1.AddLineGraph(Chart1DataSource, Colors.Blue, 2, "Lift");
Chart1.Viewport.AutoFitToView = true;
}
private void UpdateChart()
{
for (int i = 0, count = itsDAQ.getStreamCount(); i < count; i++)
{
Chart1Data.Add(itsDAQ.getValue());
if(Chart1Data.Count >= 300)
{ Chart1Data.RemoveAt(0); }
}
}
InitializeCharts() is called once when creating the window.
UpdateChart() is called on a timer event.
WindDAQ.WindDataPoint contains Lift, Drag, Velocity, and Time data. Lift and Time are shown selected.
you should use the AppendAsync method of your observableCollection.
You'r updating only the list used to create the observable one, not the source of your graph.
private void UpdateChart()
{
for (int i = 0, count = itsDAQ.getStreamCount(); i < count; i++)
{
Chart1DataSource.AppendAsync(itsDAQ.getValue());
if (Chart1DataSource.Collection.Count >= 300) // this part should work in a thread-safe
{
Chart1DataSource.Collection.RemoveAt(0); // context and with some precaution
}
}
}

How to disable a Horizontal Scrollbar in C# System.Windows.Forms.Panel

I tried adding the code from this previous thread, but it did not work for me.
How do I disable the horizontal scrollbar in a Panel
I simply just want to remove it, because it shows up regardless of if it needs to be or not be on my screen.
A panel will not show bars unless it is either statically set to do so via Panel.AutoScroll = false and panel1.HorizontalScroll.Visible = true. I would reccomend you verify that no controls extend beyond the panel, rather than forcing the status.
Insert the following into some part of your form. This will verify that you don't have a control that extends beyond the sides of the panel. Change panel1 to the name of the panel that has issues.
foreach (Control comp in panel1.Controls)
{
if (comp.Right >= panel1.Width || comp.Bottom >= panel1.Height)
{
System.Diagnostics.Debugger.Break();
}
}
If you still cannot find the issue, Panel.AutoScroll = false and panel1.HorizontalScroll.Visible = false should do the job.
I found this solution is prefered.
public class MongoDataPanel : Panel
{
[DllImport("user32.dll")]
static public extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow);
private const int SB_HORZ = 0;
private const int SB_VERT = 1;
protected override void OnResize(EventArgs eventargs)
{
base.OnResize(eventargs);
if (this.Parent != null)
{
if (this.Parent.Width > this.PreferredSize.Width - 10)
{
try
{
ShowScrollBar(this.Handle, SB_HORZ, false);
}
catch (Exception e) { }
}
}
}
}

Access dynamic control from class in a thread

I'm working on a small project which deals with polling devices to check states of I/O controls. I had implemented a small project dealing with a particular device, but have decided that i would like to eventually implement different devices, and so have moved over to an class : interface approach. This has caused a few problems however, since i moved a lot of code around.
Before i moved the code around and such, i was accessing dynamic form controls by using a delegate as such;
if (result != null)
{
this.Invoke((MethodInvoker)delegate
{
txtOutput1.Text = (result[4] == 0x00 ? "HIGH" : "LOW"); // runs on UI thread
if (result[4] == 0x00)
{
this.Controls["btn" + buttonNumber].BackColor = Color.Green;
}
else
{
this.Controls["btn" + buttonNumber].BackColor = Color.Red;
}
});
}
This worked fine until i moved certain methods to a new class which inherits from an interface. I don't want to just set the dynamic buttons to public, and i'm not sure i can create get;set; for dynamic buttons, considering theres lots of them and are created on startup. Another problem is the this.invoke" command. I believe the invoke command doesn't work unless it's placed on a form...and now it's been moved to a class, so i need to look at another way of doing this.
Does anyone have any ideas as to where i should be heading with this?
EDIT 1:
The program is designed as a monitoring system for hardware devices that handle inputs/outputs. using these i can check if, for example, a door alarm has been triggered and such. The program itself in terms of forms / design is very simple. Currently i have a single form, which generates buttons based on information in a database, for example if there are 10 devices configured, there are 10 buttons. each of these shows green / red dependant on the hardware state.
My main form triggers a thread for each device which monitors it, but because i wished to have multiple types of device i moved them to different classes and an interface which handles all of the common methods. Currently i have a device class, which implements an interface. With regards to this question, i need to now access an instance of the single main form from which i am updating, rather than creating a new instance, so that i can use the new method i created when i moved said logic into the form itself.
EDIT 2:
IdeviceInterface bfdeviceimp = new bf2300deviceimp();
// some other declarations and initialize components
private void btnConnect_Click(object sender, EventArgs e)
{
updateUI();
}
public void updateUI()
{
DBConnector mDBConnector = new DBConnector();
int count = mDBConnector.Count() - 1;
DataTable dataTable = mDBConnector.Select("SELECT * FROM devices");
int x = 12;
int y = 65;
for (int i = 0; i <= count && i < 25; i++)
{
Button btnAdd = new Button();
btnAdd.Text = dataTable.Rows[i]["deviceDescription"].ToString();
btnAdd.Location = new Point(x, y);
btnAdd.Tag = i;
btnAdd.Name = "btn" + i.ToString();
btnAdd.BackColor = Color.Green;
var temp = i + 1;
this.Controls.Add(btnAdd);
this.Controls[btnAdd.Name].MouseClick += (sender, e) =>
{
int index = temp;
generalMethods.generatePopup(sender, e, index);
};
string address = dataTable.Rows[i]["deviceIP"].ToString();
int port = int.Parse(dataTable.Rows[i]["devicePort"].ToString());
ThreadStart workerThread = delegate { start(address, port, i); };
new Thread(workerThread).Start();
x = (x + 75);
if (i != 0 && (i % 5) == 0)
{
x = 12;
y = y + 30;
}
if (i == 25)
{
Button btnPreviousPage = new Button();
btnPreviousPage.Text = "<";
btnPreviousPage.Location = new Point(150, 350);
btnPreviousPage.Tag = "left";
this.Controls.Add(btnPreviousPage);
Button btnNextPage = new Button();
btnNextPage.Text = ">";
btnNextPage.Location = new Point(225, 350);
btnNextPage.Tag = "right";
this.Controls.Add(btnNextPage);
}
}
}
public void start(string address, int port, int i)
{
if (timer == null)
{
timer = new System.Timers.Timer(1000);
timer.Elapsed += delegate(object sender, ElapsedEventArgs e) { timerElapsed(sender, e, address, port, i); };
}
timer.Enabled = true;
// MessageBox.Show("Thread " + i + " Started.");
}
public void timerElapsed(object sender, ElapsedEventArgs e, string address, int port, int i)
{
bfdeviceimp.newconnect(address, port, i);
}
and then finally my device class:
class bf2300deviceimp : IdeviceInterface
{
public void newconnect(string address, int port, int buttonNumber)
{
//send data
byte[] bData = new byte[71];
bData[0] = 240;
bData[1] = 240;
bData[2] = 0;
bData[3] = 1;
bData[68] = 240;
bData[69] = 240;
bData[70] = this.newCalculateCheckSum(bData);
try
{
byte[] result = this.newSendCommandResult(address, port, bData, 72);
//form1.setAlarmColour(result, buttonNumber);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
Where would you suggest i put the statechanged handler?
You should to use an events-based approach for solving this problem, as is often the case when it comes to passing information between forms. Each of your devices should have a custom event that they define which is fired when the state of that device changes. The event should probably just be defined in the interface for interacting with that device. The form, when it creates the various device classes should subscribe to the event and in the event handler it should update the button/textbox appropriately.
This might be a fair bit to take in if you're not used to this style of programming. Feel free to ask for more details in the comments and I can elaborate on why I did something the way I did or what it actually does.
public Form1()
{
InitializeComponent();
//not sure if this is on initialization or in a button click event handler or wherever.
IDevice device = new SomeDevice();
device.StatusChanged += GetHandlerForDevice(1);
device.DoStuff();
IDevice device2 = new SomeDevice(); //could be another class that implements IDevice
device.StatusChanged += GetHandlerForDevice(2);
device.DoStuff();
}
/// <summary>
/// The handlers for device status changed only vary based on the button number for each one.
/// This method takes a button number and returns an event handler that uses that button number.
/// </summary>
/// <param name="buttonNumber"></param>
/// <returns></returns>
private EventHandler<StatusChangedEventArgs> GetHandlerForDevice(int buttonNumber)
{
//use currying so that the event handler which doesn't have an appropriate signature
//can be attached to the status changed event.
return (sender, args) => device_StatusChanged(sender, args, buttonNumber);
}
private void device_StatusChanged(object sender, StatusChangedEventArgs args, int buttonNumber)
{
this.Invoke((MethodInvoker)delegate
{
txtOutput1.Text = (args.CurrentStatus == IDevice.Status.Green ? "HIGH" : "LOW"); // runs on UI thread
if (args.CurrentStatus == IDevice.Status.Green)
{
this.Controls["btn" + buttonNumber].BackColor = Color.Green;
}
else
{
this.Controls["btn" + buttonNumber].BackColor = Color.Red;
}
});
}
public interface IDevice
{
event EventHandler<StatusChangedEventArgs> StatusChanged;
Status CurrentStatus { get; }
public enum Status
{
Green,
Red
}
void DoStuff();
// rest of interface ...
}
public class StatusChangedEventArgs : EventArgs
{
public IDevice.Status CurrentStatus { get; set; }
//can add additional info to pass from an IDevice to a form if needed.
}
public class SomeDevice : IDevice
{
public event EventHandler<StatusChangedEventArgs> StatusChanged;
private IDevice.Status _currentStatus;
/// <summary>
/// Gets the current status of the device this object represents.
/// When set (privately) it fires the StatusChanged event.
/// </summary>
public IDevice.Status CurrentStatus
{
get { return _currentStatus; }
private set
{
_currentStatus = value;
if (StatusChanged != null)
{
StatusChangedEventArgs args = new StatusChangedEventArgs();
args.CurrentStatus = value;
StatusChanged(this, args);
}
}
}
public void DoStuff()
{
//... do stuff
CurrentStatus = IDevice.Status.Green; //will fire status changed event
}
}
Move all the logic inside a method in the form and use it externally.
In your form create a property
public SynchronizationContext SyncContext { get; set;}
In form constructor add:
this.SyncContext = WindowsFormsSynchronizationContext.Current;
Make interface:
public Interface IChangeClient
{
void Process(<some_type> result); // place your logic here
}
(or somethng like that) and implement it in your Form to change your buttons and text.
Extend your original interface to use (maybe as a parameter) SynchronizationContext and IBackgroundChangeClient.
And than your code would look like this:
if (result != null)
{
oSyncContext.Post(new System.Threading.SendOrPostCallback(
delegate(object state)
{
IBackgroundChangeClient client = (state as object[])[0] as IBackgroundChangeClient
//i dont konw the type of this
var innerResult= (state as object[])[1];
client.Process(innerResult);
}), new object[] { oBackgroundChangeClient, result[4]});
}

How to hide TabPage from TabControl [duplicate]

This question already has answers here:
Hiding and Showing TabPages in tabControl
(21 answers)
Closed 7 years ago.
How to hide TabPage from TabControl in WinForms 2.0?
No, this doesn't exist. You have to remove the tab and re-add it when you want it. Or use a different (3rd-party) tab control.
Code Snippet for Hiding a TabPage
private void HideTab1_Click(object sender, EventArgs e)
{
tabControl1.TabPages.Remove(tabPage1);
}
Code Snippet for Showing a TabPage
private void ShowTab1_Click(object sender, EventArgs e)
{
tabControl1.TabPages.Add(tabPage1);
}
I realize the question is old, and the accepted answer is old, but ...
At least in .NET 4.0 ...
To hide a tab:
tabControl.TabPages.Remove(tabPage);
To put it back:
tabControl.TabPages.Insert(index, tabPage);
TabPages works so much better than Controls for this.
Visiblity property has not been implemented on the Tabpages, and there is no Insert method also.
You need to manually insert and remove tab pages.
Here is a work around for the same.
http://www.dotnetspider.com/resources/18344-Hiding-Showing-Tabpages-Tabcontrol.aspx
Variant 1
In order to avoid visual klikering you might need to use:
bindingSource.RaiseListChangeEvent = false
or
myTabControl.RaiseSelectedIndexChanged = false
Remove a tab page:
myTabControl.Remove(myTabPage);
Add a tab page:
myTabControl.Add(myTabPage);
Insert a tab page at specific location:
myTabControl.Insert(2, myTabPage);
Do not forget to revers the changes:
bindingSource.RaiseListChangeEvent = true;
or
myTabControl.RaiseSelectedIndexChanged = true;
Variant 2
myTabPage.parent = null;
myTabPage.parent = myTabControl;
Solutions provided so far are way too complicated.
Read the easiest solution at:
http://www.codeproject.com/Questions/614157/How-to-Hide-TabControl-Headers
You could use this method to make them invisible at run time:
private void HideAllTabsOnTabControl(TabControl theTabControl)
{
theTabControl.Appearance = TabAppearance.FlatButtons;
theTabControl.ItemSize = new Size(0, 1);
theTabControl.SizeMode = TabSizeMode.Fixed;
}
I combined the answer from #Jack Griffin and the one from #amazedsaint (the dotnetspider code snippet respectively) into a single TabControlHelper.
The TabControlHelper lets you:
Show / Hide all tab pages
Show / Hide single tab pages
Keep the original position of the tab pages
Swap tab pages
public class TabControlHelper
{
private TabControl _tabControl;
private List<KeyValuePair<TabPage, int>> _pagesIndexed;
public TabControlHelper(TabControl tabControl)
{
_tabControl = tabControl;
_pagesIndexed = new List<KeyValuePair<TabPage, int>>();
for (int i = 0; i < tabControl.TabPages.Count; i++)
{
_pagesIndexed.Add(new KeyValuePair<TabPage, int> (tabControl.TabPages[i], i ));
}
}
public void HideAllPages()
{
for (int i = 0; i < _pagesIndexed.Count; i++)
{
_tabControl.TabPages.Remove(_pagesIndexed[i].Key);
}
}
public void ShowAllPages()
{
for (int i = 0; i < _pagesIndexed.Count; i++)
{
_tabControl.TabPages.Add(_pagesIndexed[i].Key);
}
}
public void HidePage(TabPage tabpage)
{
if (!_tabControl.TabPages.Contains(tabpage)) return;
_tabControl.TabPages.Remove(tabpage);
}
public void ShowPage(TabPage tabpage)
{
if (_tabControl.TabPages.Contains(tabpage)) return;
InsertTabPage(GetTabPage(tabpage).Key, GetTabPage(tabpage).Value);
}
private void InsertTabPage(TabPage tabpage, int index)
{
if (index < 0 || index > _tabControl.TabCount)
throw new ArgumentException("Index out of Range.");
_tabControl.TabPages.Add(tabpage);
if (index < _tabControl.TabCount - 1)
do
{
SwapTabPages(tabpage, (_tabControl.TabPages[_tabControl.TabPages.IndexOf(tabpage) - 1]));
}
while (_tabControl.TabPages.IndexOf(tabpage) != index);
_tabControl.SelectedTab = tabpage;
}
private void SwapTabPages(TabPage tabpage1, TabPage tabpage2)
{
if (_tabControl.TabPages.Contains(tabpage1) == false || _tabControl.TabPages.Contains(tabpage2) == false)
throw new ArgumentException("TabPages must be in the TabControls TabPageCollection.");
int Index1 = _tabControl.TabPages.IndexOf(tabpage1);
int Index2 = _tabControl.TabPages.IndexOf(tabpage2);
_tabControl.TabPages[Index1] = tabpage2;
_tabControl.TabPages[Index2] = tabpage1;
}
private KeyValuePair<TabPage, int> GetTabPage(TabPage tabpage)
{
return _pagesIndexed.Where(p => p.Key == tabpage).First();
}
}
Example on how to use it:
TabControl myTabControl = new TabControl();
TabControlHelper myHelper = new TabControlHelper(myTabControl);
myHelper.HideAllPages();
myHelper.ShowAllPages();
private System.Windows.Forms.TabControl _tabControl;
private System.Windows.Forms.TabPage _tabPage1;
private System.Windows.Forms.TabPage _tabPage2;
...
// Initialise the controls
...
// "hides" tab page 2
_tabControl.TabPages.Remove(_tabPage2);
// "shows" tab page 2
// if the tab control does not contain tabpage2
if (! _tabControl.TabPages.Contains(_tabPage2))
{
_tabControl.TabPages.Add(_tabPage2);
}
Create a new empty class and past this inside it:
using System.Windows.Forms;
namespace ExtensionMethods
{
public static class TabPageExtensions
{
public static bool IsVisible(this TabPage tabPage)
{
if (tabPage.Parent == null)
return false;
else if (tabPage.Parent.Contains(tabPage))
return true;
else
return false;
}
public static void HidePage(this TabPage tabPage)
{
TabControl parent = (TabControl)tabPage.Parent;
parent.TabPages.Remove(tabPage);
}
public static void ShowPageInTabControl(this TabPage tabPage,TabControl parent)
{
parent.TabPages.Add(tabPage);
}
}
}
2- Add reference to ExtensionMethods namespace in your form code:
using ExtensionMethods;
3- Now you can use yourTabPage.IsVisible(); to check its visibility, yourTabPage.HidePage(); to hide it, and yourTabPage.ShowPageInTabControl(parentTabControl); to show it.
you can set the parent of the tabpage to null for hiding
and to show just set tabpage parent to the tabcontrol
public static Action<Func<TabPage, bool>> GetTabHider(this TabControl container) {
if (container == null) throw new ArgumentNullException("container");
var orderedCache = new List<TabPage>();
var orderedEnumerator = container.TabPages.GetEnumerator();
while (orderedEnumerator.MoveNext()) {
var current = orderedEnumerator.Current as TabPage;
if (current != null) {
orderedCache.Add(current);
}
}
return (Func<TabPage, bool> where) => {
if (where == null) throw new ArgumentNullException("where");
container.TabPages.Clear();
foreach (TabPage page in orderedCache) {
if (where(page)) {
container.TabPages.Add(page);
}
}
};
}
Use it like this:
var hider = this.TabContainer1.GetTabHider();
hider((tab) => tab.Text != "tabPage1");
hider((tab) => tab.Text != "tabpage2");
The original ordering of the tabs is kept in a List that is completely hidden inside the anonymous function. Keep a reference to the function instance and you retain your original tab order.
Well, if you don't want to mess up existing code and just want to hide a tab, you could modify the compiler generated code to comment the line which adds the tab to the tabcontrol.
For example:
The following line adds a tab named "readformatcardpage" to a Tabcontrol named "tabcontrol"
this.tabcontrol.Controls.Add(this.readformatcardpage);
The following will prevent addition of the tab to the tabcontrol
//this.tabcontrol.Controls.Add(this.readformatcardpage);
+1 for microsoft :-) .
I managed to do it this way:
(it assumes you have a Next button that displays the next TabPage - tabSteps is the name of the Tab control)
At start up, save all the tabpages in a proper list.
When user presses Next button, remove all the TabPages in the tab control, then add that with the proper index:
int step = -1;
List<TabPage> savedTabPages;
private void FMain_Load(object sender, EventArgs e) {
// save all tabpages in the list
savedTabPages = new List<TabPage>();
foreach (TabPage tp in tabSteps.TabPages) {
savedTabPages.Add(tp);
}
SelectNextStep();
}
private void SelectNextStep() {
step++;
// remove all tabs
for (int i = tabSteps.TabPages.Count - 1; i >= 0 ; i--) {
tabSteps.TabPages.Remove(tabSteps.TabPages[i]);
}
// add required tab
tabSteps.TabPages.Add(savedTabPages[step]);
}
private void btnNext_Click(object sender, EventArgs e) {
SelectNextStep();
}
Update
public class TabControlHelper {
private TabControl tc;
private List<TabPage> pages;
public TabControlHelper(TabControl tabControl) {
tc = tabControl;
pages = new List<TabPage>();
foreach (TabPage p in tc.TabPages) {
pages.Add(p);
}
}
public void HideAllPages() {
foreach(TabPage p in pages) {
tc.TabPages.Remove(p);
}
}
public void ShowAllPages() {
foreach (TabPage p in pages) {
tc.TabPages.Add(p);
}
}
public void HidePage(TabPage tp) {
tc.TabPages.Remove(tp);
}
public void ShowPage(TabPage tp) {
tc.TabPages.Add(tp);
}
}
TabPage pageListe, pageDetay;
bool isDetay = false;
private void btnListeDetay_Click(object sender, EventArgs e)
{
if (isDetay)
{
isDetay = false;
tc.TabPages.Remove(tpKayit);
tc.TabPages.Insert(0,pageListe);
}
else
{
tc.TabPages.Remove(tpListe);
tc.TabPages.Insert(0,pageDetay);
isDetay = true;
}
}
As a cheap work around, I've used a label to cover up the tabs I wanted to hide.
We can then use the visible prop of the label as a substitute. If anyone does go this route, don't forget to handle keyboard strokes or visibility events. You wouldn't want the left right cursor keys exposing the tab you're trying to hide.
Not sure about "Winforms 2.0" but this is tried and proven:
http://www.mostthingsweb.com/2011/01/hiding-tab-headers-on-a-tabcontrol-in-c/
In WPF, it's pretty easy:
Assuming you've given the TabItem a name, e.g.,
<TabItem Header="Admin" Name="adminTab" Visibility="Hidden">
<!-- tab content -->
</TabItem>
You could have the following in the code behind the form:
if (user.AccessLevel == AccessLevelEnum.Admin)
{
adminTab.Visibility = System.Windows.Visibility.Visible;
}
It should be noted that a User object named user has been created with it's AccessLevel property set to one of the user-defined enum values of AccessLevelEnum... whatever; it's just a condition by which I decide to show the tab or not.
I also had this question. tabPage.Visible is not implemented as stated earlier, which was a great help (+1). I found you can override the control and this will work. A bit of necroposting, but I thought to post my solution here for others...
[System.ComponentModel.DesignerCategory("Code")]
public class MyTabPage : TabPage
{
private TabControl _parent;
private bool _isVisible;
private int _index;
public new bool Visible
{
get { return _isVisible; }
set
{
if (_parent == null) _parent = this.Parent as TabControl;
if (_parent == null) return;
if (_index < 0) _index = _parent.TabPages.IndexOf(this);
if (value && !_parent.TabPages.Contains(this))
{
if (_index > 0) _parent.TabPages.Insert(_index, this);
else _parent.TabPages.Add(this);
}
else if (!value && _parent.TabPages.Contains(this)) _parent.TabPages.Remove(this);
_isVisible = value;
base.Visible = value;
}
}
protected override void InitLayout()
{
base.InitLayout();
_parent = Parent as TabControl;
}
}
I've used the same approach but the problem is that when tab page was removed from the tab control TabPages list, it is removed from the tab page Controls list also. And it is not disposed when form is disposed.
So if you have a lot of such "hidden" tab pages, you can get windows handle quota exceeded error and only application restart will fix it.
If you are talking about AjaxTabControlExtender then set TabIndex of every tabs and set Visible property True/False according to your need.
myTab.Tabs[1].Visible=true/false;
// inVisible
TabPage page2 = tabControl1.TabPages[0];
page2.Visible= false;
//Visible
page2.Visible= true;
// disable
TabPage page2 = tabControl1.TabPages[0];
page2.Enabled = false;
// enable
page2.Enabled = true;
//Hide
tabCtrlTagInfo.TabPages(0).Hide()
tabCtrlTagInfo.TabPages(0).Show()
Just copy paste and try it,the above code has been tested in vs2010, it works.
Hide TabPage and Remove the Header:
this.tabPage1.Hide();
this.tabPage3.Hide();
this.tabPage5.Hide();
tabControl1.TabPages.Remove(tabPage1);
tabControl1.TabPages.Remove(tabPage3);
tabControl1.TabPages.Remove(tabPage5);
Show TabPage and Visible the Header:
tabControl1.TabPages.Insert(0,tabPage1);
tabControl1.TabPages.Insert(2, tabPage3);
tabControl1.TabPages.Insert(4, tabPage5);
this.tabPage1.Show();
this.tabPage3.Show();
this.tabPage5.Show();
tabControl1.SelectedTab = tabPage1;

Categories

Resources