I've got a DataGrid in a Silverlight application with data in it. There's also a button on our app that can change the layout such that the DataGrid changes width.
The problem is when you are in the "small" mode and scroll around, for columns that you can't see, their DataGridCell elements get Visibility=Collapsed. Now this is fine when you are scrolling around, but when the size of the DataGrid increases to show all columns, they stay collapsed. However for some reason you can always see all the cells in the first row.
This smells like a bug in the DataGrid control. Does anyone know a way to work around it? I've got as far as adding a handler for SizeChanged on the DataGrid, but I'm not quite sure what to call to force it to show those collapsed cells.
EDIT: After some testing I've found that it only happens with GridLinesVisibility="Horizontal"
XAML (Clicking toggles width of DataGrid from half to full):
<StackPanel Orientation="Vertical">
<data:DataGrid x:Name="testDataGrid" GridLinesVisibility="Horizontal" />
<Button HorizontalAlignment="Center" Content="Switch Width" Name="switchWidthButton" Click="Button_Click" />
</StackPanel>
Update: Though it can still happen sometimes with GridLinesVisibilty not set. I have not worked out exactly what circumstances this happens under.
After some discussion on the MS alias I've found that it is in fact a bug in the DataGrid. I've sent them a repro project so hopefully it will be fixed in the future. In the meantime as a workaround, you can hide and re-show one of the columns when the DataGrid changes size:
<data:DataGrid x:Name="testDataGrid" GridLinesVisibility="Horizontal" Width="150" SizeChanged="testDataGrid_SizeChanged" />
-
private void testDataGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.testDataGrid.Columns[0].Visibility = Visibility.Collapsed;
this.testDataGrid.Columns[0].Visibility = Visibility.Visible;
}
Related
I'm working on a application where the bulk of my content is presented to users with the built in WPF DataGrid. Here is what my DataGrid looks like. I don't set anything but the RowDefinitions on the parent Grid.
<UserControl xmlns="blah blah blah">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid x:Name="dgrMaterialCollection"
IsReadOnly="True"
ItemsSource="{Binding Path=MyObservableCollection, UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="True"
AutoGeneratingColumn="dgrMaterialCollection_AutoGeneratingColumn"
AutoGeneratedColumns="dgrMaterialCollection_AutoGeneratedColumns"
CanUserResizeColumns="True"
CanUserReorderColumns="True"
CanUserSortColumns="True"
EnableRowVirtualization="True"
EnableColumnVirtualization="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.VirtualizationMode="Standard"
VirtualizingPanel.IsVirtualizing="True"
SelectionMode="Single"
SelectionUnit="FullRow"
ScrollViewer.CanContentScroll="False"
RowHeaderWidth="0"
Grid.Row="0"
RowHeight="32"
RowDetailsVisibilityMode="Collapsed">
<DataGrid.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="DataGridStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!-- A few value converters -->
</ResourceDictionary>
</DataGrid.Resources>
<!-- Some Manually Declared Columns -->
</DataGrid>
<StackPanel Grid.Row="1">
<SomeTextBoxes/>
</StackPanel>
</Grid>
</UserControl>
When my application displays the DataGrid, there is about a 10 - 20 second delay depending the size of the dataset. During this delay the UI is locked up. Once the data grid is loaded - I can scroll through all few thousand items without any delay. When I say without delay, I mean clicking on the scroll bar and moving it up and down as fast as humanly possible, with no delay in rendering the rows as it scrolls. It seems like the virtualization is not working, and instead the datagrid is creating all the rows at one time instead of while the user is scrolling.
My Datagrid is nested in the following structure (spread out over multiple xaml files). There are no extra ScrollViewers, StackPanels, or anything. Just this.
<RibbonWindow>
<Grid>
<ContentControl> <!-- Populated via a DataTemplate and ContentTemplateSelector -->
<UserControl>
<UserControl>
<UserControl>
<Grid>
<Here is the DataGrid>
Some things I have attempted to speed up the loading
Explicitly set all the column widths (shaved about 10 second off my load time)
Turned off AutoGenerateColumns (didn't make a difference).
Disabling the styles set in the DataGrid's RessourceDictionary (no difference)
Load items into my ObservableCollection on a background thread, using async/await, and other multithreaded strategies. This has made no difference in load times.
Add items to a new ObservableCollection, then setting it to the databound property (so the events aren't triggered)
Add items to the ObservableCollection one at a time (events are triggered upon each add)
Turning virtualization on and off makes no difference in load time.
I know the delay is not caused by populating the ObservableCollection. It starts after the collection is fully populated, and when the WPF DataGridis rendering all the rows/columns. I have virtualization enabled and it still takes forever. I am using the .NET framework 4.5.1 (so the 4.6 issue with Virtualizationis not causing this). There are also long delays when sorting (using built in datagrid sorting) and resizing columns from the UI.
What am I missing here? Is there something I can do to ensure Virtualizationis working? What are some things that might speed this up? If I know Virtualization is working then I may move onto a new strategy, like paging or some kind of lazy loading. Another solution I would be willing to work with is one where the user interface is not blocked/frozen while all the rows are being rendered. That way the users could at least click around on other thing while rows are added in.
The ScrollViewer.CanContentScroll value on the DataGrid element must be set to True in order for row virtualization to work properly. Here is an explanation of why it must be set this way.
Thanks to ChrisW for catching that in the comments on this question.
I'm using Caliburn.Micro as a MVVM framework and I have an app that has a TabControl and each Tab is a ViewModel (and View) that has a couple of buttons on it and a custom UserControl I built, that also has a button in it. All of the tabs have the same structure (they use the same ViewModel/View).
The problem is that, for some reason, when I click the button inside the custom UserControl, that resets other Tabs - the controls inside reset to their initial values, DataGrids get cleared etc. The weird thing about this is that:
it doesn't happen always, it doesn't always happen to all tabs AND it happens even if I comment out everything within the UserControl's button's Click event (so just by the Click event being raised, some and sometimes all tabs just reset for no reason).
I've read that TabControl has this weird thing where it doesn't persist data in some cases, but
a) I don't think this is the case, because the data persists fine when switching between tabs, it just disappears when I click the button
b) Even if it is the same issue, I can't really use the solutions provided by Google, because the binding of Views, ViewModels and the TabControl is done by Caliburn.Micro and I can't mess around with how it does that (so, for example, I can't make the TabControl use a new property instead of ItemSource as some posts suggest).
It looks like it just completely resets the whole view just as if the app was just launched. When I read about the persistence issues of TabControl, people usually meant that things like sorting settings, selections get cleared, but in this case the whole tab clears including the data of DataGrids and everything else. I noticed that it only re-creates the views (their constructors get called when switching back to their tabs), but the ViewModels behind the views don't!
Has anyone else experienced this before? What did you do?
I had been searching for hours and somehow completely missed this solution: Stop TabControl from recreating its children
I'm not really sure how it works, but it somehow stops the Views from getting re-created when switching tabs and pressing any buttons.
One Solution maybe to avoid using TabItems to hold your controls. Instead leave the TabItems empty, and add all of the controls that normally would go in a TabItems into the same grid element and set the Panel.ZIndex higher for the control that you want on top. Example:
<Window x:Class="testtab.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="testtab" Height="300" Width="300"
>
<Grid Name="Grid1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="Persistant State for UserControls"
Background="Blue" Foreground="Yellow"/>
<TabControl Grid.Row="1"
Name="TabControl1"
SelectionChanged="TabControl_SelectionChanged">
<TabItem Header="Page1" />
<TabItem Header="Page2" />
<TabItem Header="Page3" />
</TabControl>
<!-- ZIndex: top=1; botton=0 -->
<TextBox Name="b1" Grid.Row="2" Panel.ZIndex="1" Text="b1"/>
<TextBox Name="b2" Grid.Row="2" Panel.ZIndex="0" Text="b2"/>
<TextBox Name="b3" Grid.Row="2" Panel.ZIndex="0" Text="b3"/>
</Grid>
</Window>
Here's the related event handler:
void TabControl_SelectionChanged(
object sender,
SelectionChangedEventArgs e)
{
//need this if settings SelectedIndex on TabControl
if (!IsInitialized) return;
switch(TabControl1.SelectedIndex) {
case 0:
Panel.SetZIndex(b1, 1);
Panel.SetZIndex(b2, 0);
Panel.SetZIndex(b3, 0);
break;
case 1:
Panel.SetZIndex(b1, 0);
Panel.SetZIndex(b2, 1);
Panel.SetZIndex(b3, 0);
break;
case 2:
Panel.SetZIndex(b1, 0);
Panel.SetZIndex(b2, 0);
Panel.SetZIndex(b3, 1);
break;
}
e.Handled = true;
}
I don't have any UserControls handy... so We are using TextBox controls...
One Afterthought, You might need too also control the Visibility property for each control selecting between "Collapsed" and "Visible". In other words, if the control is not shown because its tab is not selected, then its Visibility should be set to collapsed or hidden so as not to interfere with the tab that is on top.
In WPF, I have the following:
<ListView.View>
<GridView>
<GridViewColumn
Width="Auto"
DisplayMemberBinding="{Binding SomeProperty, Mode=OneWay}"
Header="Type"/>
The problem is that this only auto-sizes the column to the visible content. This is the same behavior when double clicking the header divider as well.
I want it to resize the column according to all content. For contrast, take the Winforms DataGridView equivalent to the current Auto resizing behavior and the equivalent to the desired behavior:
this.dataGridView1.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells; // Current behavior
this.dataGridView1.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; // Desired behavior
Is this possible for a WPF ListView?
This question is different than How to autosize and right-align GridViewColumn data in WPF? and others like it because they still trigger the current resizing behavior for visible content and do not address all content.
The problem is the ListView has its ItemsPanel with virtualizing enabled by default. In this mode, only visible content will be rendered and then will trigger the auto-sizing column. So if you don't have many items in your ListView, you can turn off the virtualizing mode via the attached property VirtualizingStackPanel.IsVirtualizing, then it will work expectedly:
<ListView VirtualizingStackPanel.IsVirtualizing="false">
<!-- ... -->
</ListView>
If you don't want to turn off virtualizing, then I don't think there is some easy way to take benefit of the Width="Auto". You may have to implement that feature manually yourself. And technically we need to have knowledge about the largest width to update the column's width accordingly. We can make some for loop every time we need to update the column's width but this is expensive. We can also manage to store the largest width every time a cell has its content changed (this is the better approach). Anyway doing it manually is a pain.
I am new to WPF and xaml and I have a problem with my apps UI.
I am using this xaml code:
<ScrollViewer HorizontalAlignment="Left" Margin="252,12,0,0" Name="captchaControlsScrollableContainer" VerticalAlignment="Top">
<Grid Name="captchaControls" Width="339" Height="286">
</Grid>
</ScrollViewer>
And this code behind code that populates the grid:
captchaControls.Children.Add(new Captcha(data));
which is called more than one time
My problem is that only the first user control app apperas in the grid although in the debugger captchaControls.Children.Count is the right size and the scrollviewer's scrollbar is disabled.
Does anyone have any idea what I am doing wrong? Thank you in advance.
Your Grid in the scrollviewer is set to have 1 column and 1 row.So you will see only the last one you add so far (all others controls are "below" the last).
Take a look to the StackPanel control and maybe this tutorial will be useful.
I've been pounding away at this issue for a little while, and have only found part of the solution.
I'm trying to set up a TabControl so that I can in some cases prevent the user from changing the currently selected tab. When the user is prevented from changing the currently selected tab, then they are shown a dialog box.
I have already read the following documents:
WPF - reset ListBox scroll position when ItemsSource changes
http://wizardsofsmart.net/uncategorized/itemssourcechanged-event-using-attached-dependency-properties/
http://joshsmithonwpf.wordpress.com/2009/09/04/how-to-prevent-a-tabitem-from-being-selected/
http://social.expression.microsoft.com/Forums/en-US/wpf/thread/f7b46018-1e97-4bbe-ada8-49b75dbc1da2/
I have implemented the solution indicated in the 3rd link (though all of the above create the same error seen below). And it works, but...
Things mess up thoroughly if the user does the following:
attempts to change the tab when such an action is disallowed. The MessageBox pops up with the error.
the user clicks "OK" and is returned to the original window.
the user tries again to change the tab. No MessageBox appears.
if the user minimizes the window, and then maximizes it again, then the MessageBox that was supposed to appear earlier appears.
the user clicks "OK" and is returned to the original window... but the tab has been changed to the one they selected before, even though they should not be able to change tabs.
This is obviously not ideal behavior. Why isn't the MessageBox appearing the second time, and why is the tab changing when it should be disallowed from doing so?
If I remove the MessageBox part, it works fine.
Here is the code for the TabControl.SelectionChanged event handler:
bool _isChanging = false;
private void tabControlForNavigation_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!_isChanging && canChangeTabs.IsChecked.HasValue)
{
_isChanging = true;
bool canLeave = canChangeTabs.IsChecked.Value; //normally this would be replaced by a check in the ViewModel
if (!canLeave)
{
int prevIndex = tabControlForNavigation.Items.IndexOf(tabControlForNavigation.SelectedContent);
tabControlForNavigation.SelectedIndex = prevIndex;
MessageBox.Show("Can't change tabs!"); //if I comment out this line, everything works fine.
}
_isChanging = false;
}
}
I am using MVVM to implement this. The Window looks like this:
<Window x:Class="TestTabControlSwitching.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<CheckBox x:Name="canChangeTabs"
Content="Can Change Tabs"
IsChecked="True" />
<TabControl x:Name="tabControlForNavigation"
Grid.Row="1"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Collection}"
SelectedItem="{Binding SelectedItem}"
SelectionChanged="tabControlForNavigation_SelectionChanged"
Margin="4"
HorizontalAlignment="Stretch">
<TabControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Path=Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
I'm omitting the rest of the code for sake of brevity- there is a pretty straight-forward ViewModel structure backing the window.
As you noticed, the problem is the MessageBox inside the event handler. The focus will change to the MessageBox and you can get all kind of undesired effects. I've had my own problems with this.
Here is a couple of SO question on the same subject
WPF: Does MessageBox Break PreviewMouseDown?
Wpf stop routing event when MessageBox appear?
If you must display a message to the user then an alternate approach might be to create a new Window which you style like a MessageBox and then call Show (not ShowDialog) on it inside the event handler.
I know this post is a bit old, but I have a very easy way to accomplish this:
Use the tab_Enter event and create a method that performs your check and displays a MessageBox to the user and then set myTabs.SelectedIndex to the prior index. A simple example:
private void someTab_Enter(object sender, EventArgs e)
{
if (myCondition)
{
MessageBox.Show("Sorry, myCondition will not let you move to this tab.");
myTabs.SelectedIndex = someOtherTabIndex;
}
}
This was a very detailed question. I had the same problem you had (i.e. the message box doesn't display on 2nd or 3rd selection changed until you minimize and maximize the window) and after much debugging and multiple google searches, stumbled on the below linked MSDN forum post.
[TabControl SelectionChanged Strange Behaviour?]
Please ignore the poorly formatted question and answer. But as mentioned in the answer, putting it inside a dispatcher and focussing the selected tab after setting the index resolved the issue for me.
You are missing an easy trick. Just make focusable=False for the Tab header.
<TabItem Header="MY TAB" Focusable="False">
You could bind this property to your view model.
<TabItem Header="MY TAB" Focusable="{Binding Bool_CanHasCheeseBurger}">