I have a tabcontrol with 9 tabitems in it. Each tab has as series of TextBoxes for the user to enter data. At the bottom is a clear button, hooked up to this method:
public void ClearTextBoxes()
{
ChildControls ccChildren = new ChildControls();
foreach (object o in ccChildren.GetChildren(rvraDockPanel, 2))
{
if (o.GetType() == typeof(WatermarkTextBox) || o.GetType() == typeof(DateBox) ||
o.GetType() == typeof(DigitBox) || o.GetType() == typeof(PhoneBox))
{
WatermarkTextBox water = (WatermarkTextBox)o;
water.Text = "";
}
else if (o.GetType() == typeof(ComboBox))
{
ComboBox combo = (ComboBox)o;
combo.SelectedIndex = -1;
}
else if (o.GetType() == typeof(CheckBox))
{
CheckBox check = (CheckBox)o;
check.IsChecked = false;
}
}
}
This works perfectly fine, however I also have a MenuItem that allows the user to ClearAll tabs. Right now the clear button only clears what's on the currently selected tab, and leaves everything else alone. My thought on how to do this was to iterate through the tabitems with this loop:
for (int i = 0; i < 10; i++ )
{
tabSelection.SelectedIndex = i;
clearButton_Click(null, null);
}
It will flip through all the tabs, but won't clear anything. I have tried using Automation instead, with the same result. It just won't seem to clear anything.
ChildControls class:
class ChildControls
{
private List<object> listChildren;
public List<object> GetChildren(Visual p_vParent, int p_nLevel)
{
if (p_vParent == null)
{
throw new ArgumentNullException("Element {0} is null!", p_vParent.ToString());
}
this.listChildren = new List<object>();
this.GetChildControls(p_vParent, p_nLevel);
return this.listChildren;
}
private void GetChildControls(Visual p_vParent, int p_nLevel)
{
int nChildCount = VisualTreeHelper.GetChildrenCount(p_vParent);
for (int i = 0; i <= nChildCount - 1; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(p_vParent, i);
listChildren.Add((object)v);
if (VisualTreeHelper.GetChildrenCount(v) > 0)
{
GetChildControls(v, p_nLevel + 1);
}
}
}
}
WPF discards the entire VisualTree of a TabItem which isn't selected, so no controls actually exist on tabs that are not visible.
To maintain control values (such as Selections, Text, etc), they need to be bound to something. If they're bound to something then your Clear() method should actually clear the Bound values, not the UI values. If they're not bound to anything, then the TabControl will revert to its initial state when selected.
To get your code working you need to add the following line to your cleanup method:
tabControl.SelectedIndex = i;
--> UpdateLayout();
Button_Click(null, null);
The UpdateLayout method takes care that the TabItem is drawn and the VisualTree is available afterwards.
Generally this approach isn't nice and if you have a real Application with Business data behind I'd recommend you have a look at databinding/MVVM. The approach shouldn't be to reset your controls in the view, but to reset your bound business data in the background.
Related
I have a DatagridView with about 300 columns and 80 rows.
Each column can be of 3 different types.
There are 3 check boxes which are responsible to show/hide the columns, each check box for each column type.
private void HideColumns(DataGridView datagridview)
{
if (datagridview.DataSource == null) return;
var watch = Stopwatch.StartNew();
// Added this code further the comment
Control c = datagridview;
while (c != this)
{
c.SuspendLayout();
c = c.Parent;
}
this.SuspendLayout();
CurrencyManager currencyManager = null;
try
{
RemoveHandler(datagridview); // remove all the handlers to the datagridivew for performance issue
currencyManager = (CurrencyManager)BindingContext[datagridview.DataSource];
currencyManager.SuspendBinding();
//datagridview.Visible = false;
for (int i = 0; i < datagridview.Columns.Count; i++)
{
var column = datagridview.Columns[i];
var itemType = datagridview.Rows[(int)OrdersAndComponentsRows.ItemType].Cells[column.Index].Value.ToString();
if (itemType == Glossary.IndirectCOType )
column.Visible = IndirectCOCheckBox.Checked;
else if (itemType == Glossary.NotAllocatedType )
column.Visible = NotAllocatedCheckBox.Checked;
else
column.Visible = DirectCOCheckBox.Checked;
}
}
finally
{
//datagridview.Visible = true;
if (currencyManager != null)
currencyManager.ResumeBinding();
AddHandler(datagridview);
// Added this code further the comment
c = datagridview;
while (c != this)
{
c.ResumeLayout();
c = c.Parent;
}
this.ResumeLayout();
}
// 3 check boxes are subscribed to this event
private void DisplayColumn_CheckedChanged(object sender, EventArgs e)
{
try
{
Cursor = Cursors.WaitCursor;
HideColumns(ShipCoverageDGV);
}
finally
{
Cursor = Cursors.Default;
}
}
The problem is when I am unchecking one of the checkboxes, it takes about 50 seconds to hide the columns.
The weird thing is that when checking the checkbox, it takes around 5 seconds to show the hidden columns.
Is there any operation that can be done in order to hide columns faster?
Setting AutoSizeRowMode to DisplayedHeaders solved the problem. It takes about 1 second.
Before setting the value, this property was set to AllCells
datagridview.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedHeaders;
Note: Setting the property to None decrease the time to about 12 seconds.
The entire method including the fix:
private void HideColumns(DataGridView datagridview)
{
try
{
datagridview.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedHeaders;
for (int i = 0; i < datagridview.Columns.Count; i++)
{
var column = datagridview.Columns[i];
var itemType = datagridview.Rows[(int)OrdersAndComponentsRows.ItemType].Cells[column.Index].Value.ToString();
if (itemType == Glossary.IndirectCOType)
column.Visible = IndirectCOCheckBox.Checked;
else if (itemType == Glossary.NotAllocatedType)
column.Visible = NotAllocatedCheckBox.Checked;
else
column.Visible = DirectCOCheckBox.Checked;
}
}
finally
{
datagridview.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
}
}
I think you need to figure out which part of the code is taking time.There are lots of things occurring in your hide columns method. Can you note down the time at the more granular level, like how much time been spent on suspending layout vs iterating over columns and making the columns hide.Once you know the problematic area you can work on optimizing that one.I am assuming here the problem statement is going to be the time that been spent on iteration as we have 300 columns and checking type of each one and hiding based on their type.
So I have this and I know it is wrong:
protected override void OnSelectionChangeCommitted(EventArgs e)
{
if (SelectedIndex == 0)
{
GENIO_Viewer.FullColourPaletteForm dlgColour = new GENIO_Viewer.FullColourPaletteForm();
if(dlgColour.ShowDialog() == DialogResult.OK)
{
bool bFound = false;
for(int i = 1; i < Items.Count; i++)
{
ComboboxColourItem ocbItem = (ComboboxColourItem)Items[i];
if(ocbItem.Index == dlgColour.iSelectedColour)
{
SelectedIndex = i;
bFound = true;
break;
// We can just select this one
}
}
if(!bFound)
{
// Add it
ComboboxColourItem ocbItem = ComboboxColourItem.Create((ushort)dlgColour.iSelectedColour);
Items.Add(ocbItem);
SelectedIndex = Items.Count - 1;
}
}
}
base.OnSelectionChangeCommitted(e);
}
This handler is part of my DataGridViewComboBoxEditingControl. But it is the wrong place to add new Items.
I can't workout how to get access to the owning Column as that is where I need to add the Item, otherwise I get exceptions.
I have looked here: https://msdn.microsoft.com/en-us/library/system.windows.forms.datagridviewcomboboxeditingcontrol(v=vs.110).aspx
But i can't see a property I can use to get the column object.
How do we do this from the editing control?
Further explaination:
The list combo items are added by the "column" object. Thus we have a set of 15 colours to choose from. Now I have added a genric colour tot he top of the list.
So, the user invokes the edit, droplist displays, and they pick item 0. We intercept this with the aforementioned handler. Since they picked item 0, we show a popup dialogue to let them choose a different colour.
When they have chosen, we must now either find it or add it to the the mandatory list of items for the column. Make sense now?
I tried to use the DataGridView Notify object but for some reason it is not showing in the list of available functions.
I don't use a DataSource. I populate like this in the columns constructor:
private void InitialiseComboItems()
{
List<ushort> listColours = new List<ushort>();
listColours.Add(0);
listColours.Add(1);
listColours.Add(2);
listColours.Add(3);
listColours.Add(4);
listColours.Add(5);
listColours.Add(6);
listColours.Add(7);
listColours.Add(8);
listColours.Add(9);
listColours.Add(250);
listColours.Add(251);
listColours.Add(252);
listColours.Add(253);
listColours.Add(254);
listColours.Add(255);
this.Items.Clear();
foreach (ushort iColourIndex in listColours)
this.Items.Add(ComboboxColourItem.Create(iColourIndex));
}
I also have a helper method:
public ComboboxColourItem InsertColour(ushort iColourIndex)
{
ComboboxColourItem ocbItem = ComboboxColourItem.Create(iColourIndex);
bool bAppend = true;
if (Items.Count > 16)
{
// There are other colours, need to find right index
for(int i = 16; i < Items.Count; i++)
{
if(ocbItem.Index < ((ComboboxColourItem)Items[i]).Index)
{
bAppend = false;
Items.Insert(i, ocbItem);
break;
}
}
}
if (bAppend)
Items.Add(ocbItem);
return ocbItem;
}
You can use EditingControlDataGridView to find the DataGridView which owns the editing control. Then you can use CurrentCell property of grid to find the current cell and using ColumnIndex you will find the column index. Then using Columns collection, you can get the column at that index:
var c = this.EditingControlDataGridView
.Columns[this.EditingControlDataGridView.CurrentCell.ColumnIndex]
as DataGridViewComboBoxColumn;
if (c != null)
c.Items.Add("Something");
I have a DataGrid where the ItemsSource is bound to an ObservableCollection<LogEntry>. On a click on a Button the user can scroll to a specific LogEntry. Therefor I use the following code:
private void BringSelectedItemIntoView(LogEntry logEntry)
{
if (logEntry != null)
{
ContentDataGrid.ScrollIntoView(logEntry);
}
}
This just works fine. But what I don't like is: If the LogEntry already is in view then the DataGrid flickers shortly.
My question now is:
Is there a possibility to check on the DataGrid if the given LogEntry already is in view?
You can get index for first visible item and last visible item
then you can check if your item's index was within first and last or not.
var verticalScrollBar = GetScrollbar(DataGrid1, Orientation.Vertical);
var count = DataGrid1.Items.Count;
var firstRow = verticalScrollBar.Value;
var lastRow = firstRow + count - verticalScrollBar.Maximum;
// check if item index is between first and last should work
Get Scrollbar method
private static ScrollBar GetScrollbar(DependencyObject dep, Orientation orientation)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dep); i++)
{
var child = VisualTreeHelper.GetChild(dep, i);
var bar = child as ScrollBar;
if (bar != null && bar.Orientation == orientation)
return bar;
else
{
ScrollBar scrollBar = GetScrollbar(child, orientation);
if (scrollBar != null)
return scrollBar;
}
}
return null;
}
I have a listbox , I add items dynamically to the listbox.
I want the listbox to autoscroll to the last item added.
I used
List<string> ItemsList = new List<string>
public void InsertItem(string newItem)
{
ItemsList.Add(status);
if (ItemsList.Count > MaxSize)
{
ItemsList.RemoveAt(0);
}
lb.Items.Refresh();
lb.SelectedIndex = lb.Items.Count - 1;
lb.ScrollIntoView(status);
}
but this works only before my application is initialized (i.e I added some items before the application starts)
But after the application has started if I try adding items,the scroll bar is not auto scrolling to the last added item
Could any one tell solution for this
This has turned out to be quite a mission actually, because the ScrollIntoView only works the first time it is called. Every other call after that will not work for some reason.
The way around this, would be to find the "ScrollInfo" of the listbox and set the scroll value. See example below
public static void AutoScrollToCurrentItem(ListBox listBox, int index)
{
// Find a container
UIElement container = null;
for (int i = index; i > 0; i--)
{
container = listBox.ItemContainerGenerator.ContainerFromIndex(i) as UIElement;
if (container != null)
{
break;
}
}
if (container == null)
return;
// Find the ScrollContentPresenter
ScrollContentPresenter presenter = null;
for (Visual vis = container; vis != null && vis != listBox; vis = VisualTreeHelper.GetParent(vis) as Visual)
if ((presenter = vis as ScrollContentPresenter) != null)
break;
if (presenter == null)
return;
// Find the IScrollInfo
var scrollInfo =
!presenter.CanContentScroll ? presenter :
presenter.Content as IScrollInfo ??
FirstVisualChild(presenter.Content as ItemsPresenter) as IScrollInfo ??
presenter;
// Find the amount of items that is "Visible" in the ListBox
var height = (container as ListBoxItem).ActualHeight;
var lbHeight = listBox.ActualHeight;
var showCount = (int)(lbHeight / height) - 1;
//Set the scrollbar
if (scrollInfo.CanVerticallyScroll)
scrollInfo.SetVerticalOffset(index - showCount);
}
private static DependencyObject FirstVisualChild(Visual visual)
{
if (visual == null) return null;
if (VisualTreeHelper.GetChildrenCount(visual) == 0) return null;
return VisualTreeHelper.GetChild(visual, 0);
}
The way to use the above code can be like this:
private void Button_Click(object sender, RoutedEventArgs e)
{
listBox1.Items.Add("New Item");
AutoScrollToCurrentItem(listBox1, listBox1.Items.Count);
}
I really wish I had enough points to comment on your question. How is your ItemsList connected to your ListBox? After you start your application, can you manually scroll down to the new items?
Most likely you binded ItemsList to ListBox. If so, I suggest you change List<string> to ObservableCollection<string> so that ListBox can automatically update itself.
ObservableCollection
if u can update listbox automatically then use ObservableCollection.
private ObservableCollection<string> _source = new ObservableCollection<string>();
and your insert method:
private void Insert(string item)
{
_source.Add(item);
lb.SelectedIndex = _source.Count - 1;
lb.ScrollIntoView(ListBox.SelectedItem);
}
When the following function is called, it is not showing the corresponding photoBoxes. I've done a debugging walkthrough, it even reaches the parts necessary to Show() and Hide(). I don't know what I can do. It's not showing anything
public void SmokerTakeIngredientFromTable(agents agent, List<smokers> smoker)
{
int index = 0;
bool smoker_takes_ingredients = false;
while (!smoker_takes_ingredients)
{
if ((smoker[index].item != agent.item_1) && (smoker[index].item != agent.item_2))
{
if (index == 0)
{
leftarrow_img.Show();
rightarrow_img.Hide();
downarrow_img.Hide();
}
else if (index == 1)
{
leftarrow_img.Hide();
rightarrow_img.Show();
downarrow_img.Hide();
}
else if (index == 2)
{
leftarrow_img.Hide();
rightarrow_img.Hide();
downarrow_img.Show();
}
agent.item_1 = 3;
agent.item_2 = 3;
break;
}
index++;
}
}
This is what the designer for these photoBoxes look like:
This is the properties page for one of the photoBoxes (they are all identical apart from the actual image file, they all have Visible = false too)
When making visibility changes I need to refresh the form using this.Refresh()