Datepicker column enabling itself when playing with vertical scrollbar in WPF - c#

I have a weird bug and i need some rescue.
I have a grid with several column of multiple types in WPF.
One or several of these columns are DatePickers that I created through a FrameElementFactory :
FrameworkElementFactory dateFactory = new FrameworkElementFactory(typeof(DatePicker));
...
column = new DataGridTemplateColumn { CellTemplate = new DataTemplate
{ VisualTree = dateFactory } };
this._mainDatagrid.Columns.Add(column);
I have put a method to disable the DatePickers of my grid on a certain state of one of my variable:
private IEnumerable<DataGridRow> GetDataGridRows(DataGrid grid)
{
//return the Datagrid Rows
}
public void SetChangeLockState(bool isUnlocked)
{
IEnumerable<DataGridRow> _rows = this.GetDataGridRows(this._mainDatagrid);
foreach (DataGridColumn _column in this._mainDatagrid.Columns)
{
if (_column.GetType() != typeof(DataGridTemplateColumn)) continue;
foreach (DataGridRow _row in _rows)
{
FrameworkElement frameworkElement = _column.GetCellContent(_row);
if (frameworkElement != null) frameworkElement.IsEnabled = !isUnlocked;
}
}
}
The problem is that when I am playing with the elevator of my grid, the Datepicker keep enabling and disabling for no reason.
Example:
All my DatePicker are enabled, I am playing with my vertical scroll bar, no problem.
All my DatePickers are Disabled, I am playing with my vertical scroll bar.
1 Datepicker will suddenly appear enable :
DatePicker enabled 1
I am keeping playing with the scrollbar and another Datepicker will go enabled :
DatePicker enabled 2
Have you any idea of what could happen ?
Thanks for your help.

This will be because DataGrid.EnableRowVirtualization defaults to true. This enables UI virtualisation, which means that UI Elements that are scrolled out of view may be disposed or reused. Thus on occasions when scrolling an item back into view a new DatePicker will be created via your factory, and of course this new DatePicker will not have existed when SetChangeLockState was called and thus will not be disabled.
A quick fix would be to set DataGrid.EnableRowVirtualization to false, but this may not be very performant if you have lots of rows. A better solution would be to bind rather than set the IsEnabled property, e.g. to a property on your Window using RelativeSource.

Thanks to Ben, here is the code :
dateFactory.SetBinding(
//My IsEnabled property I wanted to change
IsEnabledProperty,
new Binding("IsLockedDatagrid")
{
//Datagridwidget is the datagrid I am using where I can found the IsLockedDatagrid boolean variable (in my xaml)
RelativeSource =
new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGridWidget), 1),
Mode = BindingMode.OneWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});

Related

C# WinForm TabControl Formatting Issue

When programmatically adding controls to a tab control, I have been using the Form_Load event to create and embed things like datagridviews into my UI. I made a class that inherits from DataGridView
class DBDataGridView : DataGridView
{
public DBDataGridView()
{
DoubleBuffered = true;
AllowUserToAddRows = false;
AllowUserToDeleteRows = false;
AllowUserToResizeRows = false;
AllowUserToOrderColumns = false;
AllowUserToResizeColumns = false;
RowHeadersVisible = false;
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
ReadOnly = true;
Dock = DockStyle.Fill;
SelectionMode = DataGridViewSelectionMode.FullRowSelect;
TabStop = false;
}
}
And I call it later in the Form_Load event like so
private void MainDesignerForm_Load(object sender, EventArgs e)
{
DBDataGridView _DGV = new DBDataGridView();
var listOfOverlays = new List<OverlaySelectionList>()
{
new OverlaySelectionList { Description = "Description 1", PartNumber = "123-R1"},
new OverlaySelectionList { Description = "Description 2", PartNumber = "456-R1"}
};
var overlayList = new BindingList<OverlaySelectionList>(listOfOverlays);
_DGV.DataSource = overlayList;
Tab_Overlay.Controls.Add(_DGV);
_DGV.ClearSelection();
}
This gridview is on the THIRD tab of the TabControl, and everything works as expected except the ClearSelection(). No matter where I call it, it does not clear the initial row selection of the DGV. However, if I fire the same code block from a button ON the third tab, the formatting AND the ClearSelection() behave as expected.
What is causing this behavior?
Thanks to 41686d6564 and Jimi for the insight into the specifics on why this was happening.
Reiterating what they said in the comments: Assignment of properties appear to be cached regardless of whether the control they belong to is active or not (Hence why all the sizing and formatting properties were present at run time). However, actions that require a handle, like ClearSelection() require the control to be shown and active for the intended behavior to be observed.
Setting the selected tab to where the DataGridView before calling ClearSelection() was the solution (Or in my case, I had nested tabs, so I had to follow the tab tree to get to the specific tab that the DataGridView was on)
So now, part of the Load_Form logic is to check WHERE the control is located, make that tab active, THEN format and clear selections for each control that is being added. This allowed ClearSelection() to work as intended.

Binding Visibility without XAML

I am quite new to WPF, and especially to DataBinding, and it is pretty hard for me to understand, how to code it without XAML. How can I bind parents visibility (parent is scrollViewer) to its child visibility (child is grid), without XAML? Here is what I am trying to do right now:
{
//Code that creates grid
//Code that creates scrollViewer
scrollViewer.Content = grid;
LayoutRoot.Children.Add(scrollViewer); //adding it to the main window
//Creating binding
Binding myBinding = new Binding("Vis");
BooleanToVisibilityConverter c = new BooleanToVisibilityConverter();
myBinding.Source = grid.Visibility;
myBinding.Converter = c;
myBinding.Mode = BindingMode.TwoWay;
myBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(scrollViewer, VisibilityProperty, myBinding);
}
And here what is my "Vis" right now:
public Visibility Vis
{
get
{
return Visibility;
}
set
{
Visibility = value;
OnPropertyChanged(Visibility);
}
}
Ofcourse, right now problem is OnPropertyChanged, it gives me an error, and I need to put DependencyPropertyChangedEventArgs inside, but I do not know how to. I am not sure, am I doing this right way (maybe this can be simplified?), or maybe my method will not work at all, even after fixing this error. I need it to be without XAML, because controls are created dynamically.
Solution provided by PieterWitvoet worked for me. I had to set scrollViewer visibility to Visibility.Visible on its creation, but everything else works just fine.

Why does the ListView refuse to display its columns, items, and subitems (shows only Group)?

The ListView seems to be as cantankerous as a polecat and as temperamental as a [elided by the P.C. police]
With help from - and others, I was able to get a ListView working just as I wanted
it to
(http://stackoverflow.com/questions/11423537/how-to-add-subitems-to-a-listview).
Now I'm converting that simple demo for use in a real app.
I create the columns in the form's Load event:
listViewGroupsItems.Columns.Add(colHeadName);
listViewGroupsItems.Columns.Add(colHeadRate);
...but they don't display. However, there is a blank line above my first item. So why are no text values being displayed. Are they "there" but invisible? Why would they get wiped out?
Anyway, what I want to see is:
column1Title column2Title
Group1Name
Item subitem
Item subitem
Group2Name
Item subitem
Group1Name
Item subitem
Item subitem
Item subitem
Item subitem
...but what I actually see is just:
[blank line]
Group1Name
...that's it!
The ListView's View property is set to Details; otherwise, all of the properties are the default values.
My listview code is:
private void AddGroupsAndItems() {
Dictionary<string, string> GroupsDict = PlatypusData.GetGroupsForTopLevel(Convert.ToInt32(labelTopLevel.Tag));
int currentGroup = 0;
foreach (KeyValuePair<string, string> entry in GroupsDict) {
string GroupNumber = entry.Key;
string GroupName = entry.Value;
listViewGroupsItems.Groups.Add(new ListViewGroup(GroupName, HorizontalAlignment.Left));
Dictionary<string, string> ItemsDict = PlatypusData.GetItemsForGroup(GroupNumber);
foreach (KeyValuePair<string, string> itemEntry in ItemsDict) {
string itemID = itemEntry.Key;
string itemName = itemEntry.Value;
ListViewItem lvi = new ListViewItem(string.Format("{0} ({1})", itemName, itemID));
lvi.SubItems.Add(PlatypusData.GetDuckbillNameForItemId(itemID));
listViewGroupsItems.Items.Add(lvi);
listViewGroupsItems.Groups[currentGroup].Items.Add(lvi);
}
currentGroup++;
}
}
UPDATE
I changed my code in the form's Load event from:
var colHeadName = new ColumnHeader { Text = Resources.RateRequestForm_RateRequestForm_Load_Billing_Account_Client, Width = 160 };
var colHeadRate = new ColumnHeader { Text = Resources.RateRequestForm_RateRequestForm_Load_RatePlan_ID, Width = 120 };
listViewCustomerBillAccountsClients.Columns.Add(colheadName); //colHeadName);
listViewCustomerBillAccountsClients.Columns.Add(colheadRate); //colHeadRate);
...to:
ColumnHeader colheadName = new ColumnHeader();
ColumnHeader colheadRate = new ColumnHeader();
listViewCustomerBillAccountsClients.Columns.Add(colheadName);
listViewCustomerBillAccountsClients.Columns.Add(colheadRate);
...and it made no difference at all.
It would seem that the ColumnHeader constructor should be able to take a string of what it should display, but even when I do that:
ColumnHeader colheadName = new ColumnHeader("This");
ColumnHeader colheadRate = new ColumnHeader("That");
...there is still no change (according to Intellisense or whatever it's called, the string arg is an ImageKey, but I thought I'd try just out of thoroughness/frustration.
Late to the party, but I've just spent some time trying to solve a similar problem. The answer I found was that when clearing the list before filling, you must use
listviewGroupItems.Items.Clear();
not
listviewGroupItems.Clear();
The ListView.Clear() method clears everything from the control--including the columns
I had the same issue today. I had coded listView.Clear() as user2867342 mentioned. I needed to change that to listView.Items.Clear(), but that did not make the columns appear. The columns were there, and I could click on them and resize them, but they were completely blank.
I had a ListView set to Details mode. I also had set the OwnerDraw property to true, because I wanted to paint my own progress bar column. MSDN says the following about the OwnerDraw property (emphasis mine):
A ListView control is normally drawn by the operating system. In order
to customize the appearance of ListView items, subitems, and column
headers, set the OwnerDraw property to true and provide a handler for
one or more of the following events: DrawItem, DrawSubItem,
DrawColumnHeader. This is called owner drawing. When the View property
is set to View.Details, all three events occur; otherwise, only the
DrawItem event occurs.
I had to implement the DrawColumnHeader event. In my case, the defualt worked fine, so the method sets the DrawDefault event parameter to true. After implementing this event handler, the column headers appeared correctly:
...Windows.Forms designer code...
listView.DrawColumnHeader += new DrawListViewColumnHeaderEventHandler(this.listView_DrawColumnHeader);
...
private void listView_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true;
}
You're missing column headers in your code. (fixed)
Per the MSDN:
"If your ListView control does not have any column headers specified and you set the View property to View.Details, the ListView control will not display any items. If your ListView control does not have any column headers specified and you set the View property to View.Tile, the ListView control will not display any subitems."
Granted, you'll probably need to make more adjustments than what you see in my SS, but it at least answers your question as to why you are getting blanks.
Edit (Changed lines, although changing them back to your code didn't skew my successful results):
lvi.Group = listViewGroupsItems.Groups[currentGroup];
listViewGroupsItems.Items.Add(lvi);
One more thing to check I just found:
OwnerDraw must be false.
Copy & Paste from previous project and forgot that I used OwnerDraw

Unselect all rows in datagridview

I have two datagridviews, and when I click to one of them, I would like to deselect all selection in the second datagridview, I tried this, but nothing works:
firstItemsDataGridView.ClearSelection();
firstItemsDataGridView.CurrentCell = null;
not working,
firstItemsDataGridView.ClearSelection();
if (firstItemsDataGridView.Rows.Count > 0)
firstItemsDataGridView[1, 0].Selected = true;
firstItemsDataGridView.CurrentCell = null;
firstItemsDataGridView.ClearSelection();
foreach (DataGridViewRow item in firstItemsDataGridView.Rows) {
item.Selected = false;
foreach (DataGridViewCell itemCell in firstItemsDataGridView.Columns) {
itemCell.Selected = false;
}
}
not working,
firstItemsDataGridView.Rows[0,-1].Selected = true;
not working too.
I have set selecting mode to full row selection, and I have no idea how to achieve my goal. thanks a lot!
dataGridView1.ClearSelection();
Should work. Maybe you have code that auto selects rows which is triggered?
An answer at NullSkull solved the problem for me which was that the cell at row 0, column 0 for the datagridview control was always selected on form load, and could not be deselected despite calling ClearSelection within the form's constructor method. I just had to call ClearSelection in the Form_Load event.
http://www.nullskull.com/q/10066166/how-to-deselect-the-first-row-when-the-form-i-loaded-in-datagridview-in-windows-application.aspx
Since there is no answer, and I used this answer in other posts and i ran into the same issue of first row being selected and deselecting wasn't possible:
Color blue = ColorTranslator.FromHtml("#CCFFFF");
Color red = ColorTranslator.FromHtml("#FFCCFF");
Color letters = Color.Black;
foreach (DataGridViewRow r in datagridIncome.Rows)
{
if (r.Cells[5].Value.ToString().Contains("1")) {
r.DefaultCellStyle.BackColor = blue;
r.DefaultCellStyle.SelectionBackColor = blue;
r.DefaultCellStyle.SelectionForeColor = letters;
}
else {
r.DefaultCellStyle.BackColor = red;
r.DefaultCellStyle.SelectionBackColor = red;
r.DefaultCellStyle.SelectionForeColor = letters;
}
}
This is a small trick, the only way you can see a row is selected, is by the very first column (not column[0], but the one therefore). When you click another row, you will not see the blue selection anymore, only the arrow indicates which row have selected.
SOLUTION:
i found out why my first row was default selected and found out how to not select it by default.
By default my datagridview was the object with the first tab-stop on my windows form. Making the tab stop first on another object (maybe disabling tabstop for the datagrid at all will work to) disabled selecting the first row
Just use:
dgvName.ClearSelection();
I tried this and it's working for me:
for (int i = 0; i < dataGridView1.Rows.Count;i++)
{
dataGridView1.Rows[i].Selected = false;
}
Make sure all the rows are deselected (dataGridView.Rows[...].Selected = false)
Row zero defaults to selected, so set dataGridView.Rows[0].Selected = false when opening the DataGridView and as long as the other options are set so the user can't select, then you will have, and maintain, nothing selected.
// no selection in dgv at the begening
dgv.FirstDisplayedCell = null;
dgv.ClearSelection();
// try this it is working !
If your using WPF and want to maintain a MVVM design pattern you can bind both selected items to the same property so when you select one the other will automatically be deselected.
try something like this
public class SingletonClass
{
private static SingletonClass _Instance;
public static SingletonClass Instance
{
get
{
if (_Instance == null)
_Instance = new SingletonClass();
return _Instance;
}
} // End Property Instance
private object _SelectedItem;
public object SelectedItem
{
get { return _SelectedItem; }
set { _SelectedItem = value; }
} // End Selected Item
}
<DataGrid Name="Datagrid1" SelectedItem="{Binding Source={x:Static Const:SingletonClass.Instance},Path=SelectedItem,IsAsync=True}">
<DataGrid Name="Datagrid2" SelectedItem="{Binding Source={x:Static Const:SingletonClass.Instance},Path=SelectedItem,IsAsync=True}">
*I only used a singleton class because this will work across different views in different instances, you can use a regular class if your in the same view.
Please note the IsAsync=True on the xmal, if you plan on using a singleton class across diffrent views it will not work without it.
As said #Taw in subcomments to top answer - "Don't call it too soon!".
In my case default behavior not works at all. My case - datagrid in tab of tabControl and it did not work if that tab not shown before!
That hack works like a charm :
AnyTabControl.SelectedTab = FirsTab;
gridModules.ClearSelection(); //placed at first tab
AnyTabControl.SelectedTab = SecondTab; //that tab i would like to show first
As a result : second tab displayed to user, and when he clicked to first - selection will not appear.

How to scroll in a fixed-size ToolStripDropDown

I am using a ToolStripDropDown control to implement the dropdown portion of a custom ComboBox-like control. In order to be visually appealing, I am imposing a MaximumSize on the dropdown and manually specifying the width of each ToolStripButton within it - the result is a popup which is the same width as the control that activates it, with a cap on the height of the height of the dropdown portion.
Example (simplified):
ToolStripDropDown dropDown = new ToolStripDropDown();
dropDown.MaximumSize = new Size(200, 100);
dropDown.RenderMode = ToolStripRenderMode.System;
dropDown.AutoSize = true;
for (int i = 0; i < 50; i++) {
ToolStripButton dropDownItem = (ToolStripButton)dropDown.Items.Add("Item " + i);
dropDownItem.AutoSize = false;
dropDownItem.Size = new Size(200, 20);
}
dropDown.Show(owningControl, new Point(0, owningControl.Height - 1));
As you can see, the constraints on the popup's size are applied, however the up/down scroll buttons are not displayed and there seems to be no way to make them appear. There do not appear to be any methods or properties within ToolStripDropDown regarding the scrolling offset or a mechanism to scroll a particular item into view (such as EnsureVisible() on ListViewItem).
How, then, can I get the dropdown to scroll? Any method would be sufficient, be it a scroll bar, scroll buttons or even the mouse-wheel.
(Incidentally, I have tried many times to make similar controls using a Form for the dropdown portion - despite trying dozens of solutions to prevent the popup from stealing focus or gaining focus when its controls are clicked, this seems to be a dead end. I have also ruled out using ToolStripControlHost, whose hosted control can still take focus away from the form that opened it.)
Finally cracked this one. It occurred to me that ContextMenuStrip and ToolStripDropDownMenu are capable of the auto-scrolling behaviour which their base class, ToolStripDropDown, cannot provide. Initially, I avoided these alternative controls because they usually add a wide margin. This can be removed via ShowImageMargin and ShowCheckMargin. Even after doing this, a small (approx 5px) margin remains. This can be removed by overriding the DefaultPadding property:
public class MyDropDown : ToolStripDropDownMenu {
protected override Padding DefaultPadding {
get { return Padding.Empty; }
}
public MyDropDown() {
ShowImageMargin = ShowCheckMargin = false;
RenderMode = ToolStripRenderMode.System;
MaximumSize = new Size(200, 150);
}
}
// adding items and calling Show() remains the same as in the question
This results in a popup window which can contain any type of ToolStrip item, enforces MaximumSize, has no margin and, most importantly, does not steal focus and cannot receive focus.
This is your nemesis:
internal virtual bool RequiresScrollButtons
{
get
{
return false;
}
set
{
}
}
It is internal, you cannot override it. You can revive your approach of using a Form by fixing the focus stealing behavior. Paste this into the form class:
protected override bool ShowWithoutActivation
{
get { return true; }
}

Categories