WPF: UserControl with Expander leave blank space after Expander collapsed - c#

I have UserControl with Expander. And I use it in main window in ListBox like this:
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding MyCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<local:MyUserControl />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
After form loaded UserControls fill space normally.
After child user control expanded parent container's height grows, as expected.
After child user control expanded
If I collapsed UserControl, there are the blank space left in the parent container.
Blank space left after child user control collapsed
If main window gets resize event, all blank space are gone.
I'd tried use Element.InvalidateVisual and Element.InvalidateMeasure in code, and also UpdateLayout, and wrap ListBox with DockPanel + DockPanel.Dock="Top" for panel's child. It was not effect.
At other hand, when I add the same UserControl in the code, this artefact disappears: parent control collapsed as I need after child UserControl collapsed. But I need exactly XAML and UserControl as DataTemplate.
Please, help me. I sure there must be some easy solution, but I'm new to the XAML and so I have difficulty with this.

Finally, i've solved this problem. The solution is set ScrollViewer.CanContentScroll property of ListBox element to True.

The solution is to set the parent FrameworkElement with a HorizontalAligment like in the following example.
<StackPanel Background="Green" VerticalAlignment="Top" >
<Expander>
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<local:SomeUserControl></local:SomeUserControl>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander>
</StackPanel>
This works on my solution.
Your FrameworkElements behaviour around your expander is a bit weird. If you still have some issues you should show more information about it.

Related

WPF DataGrid inside a ScrollViewer

I have a StackPanel with a header control and a DataGrid Inside a Scrollviewer; like so:
<ScrollViewer>
<StackPanel Orientation="Vertical">
<Canvas x:Name="header"
Height="300" />
<DataGrid x:Name="dataGrid">
</DataGrid>
</StackPanel>
</ScrollViewer>
The scoll behavior should fulfill these requirements:
Scrolling while MouseOver the DataGrid should scroll the outer ScrollViewer
The header control (symbolized by Canvas) is scrolled by the ScollViewer.
The horizontal scrollbar at the bottom of the DataGrid should be preserved on screen at any time.
The horizontal scrollbar should not scroll the header control, only the DataGrid.
I tried various settings for the DataGrid.ScrollViewer but none have the desired effect, nor does changing the StackPanel to a WrapPanel or even Grid help any.
Is this possible? Any help and resources to read are appreciated.
I've been struggling with your first problem for some time as well - having an inner ScrollViewer (or DataGrid in this case) scroll the outer ScrollViewer. But I think I have a pretty elegant solution. You have to add an event handler to the PreviewMouseWheel event of the DataGrid (and a name to the ScrollViewer):
<ScrollViewer x:Name="scroll_viewer">
<StackPanel Orientation="Vertical">
<Canvas x:Name="header"
Height="300" />
<DataGrid x:Name="dataGrid" PreviewMouseWheel="mousewheel">
</DataGrid>
</StackPanel>
</ScrollViewer>
Then the event handling method:
private void mousewheel(object sender, MouseWheelEventArgs e)
{
//what we're doing here, is that we're invoking the "MouseWheel" event of the parent ScrollViewer.
//first, we make the object with the event arguments (using the values from the current event)
var args = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
//then we need to set the event that we're invoking.
//the ScrollViewer control internally does the scrolling on MouseWheelEvent, so that's what we're going to use:
args.RoutedEvent = ScrollViewer.MouseWheelEvent;
//and finally, we raise the event on the parent ScrollViewer.
scroll_viewer.RaiseEvent(args);
}
Hope this helps!
It's a very complicated question, it probably requires a man-week to complete! But here's a start which solves half of your problem:
<ScrollViewer x:Name="scroll">
<DataGrid x:Name="dataGrid">
<DataGrid.Template>
<ControlTemplate>
<StackPanel Orientation="Vertical">
<Canvas x:Name="header" Height="300" />
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</DataGrid.Template>
</DataGrid>
</ScrollViewer>
If you put the canvas inside the datagrid's template it will consider it as a part of the datagrid so canvas and datagrid will be scrolled as one.
Obviously, the datagrid template will be blank this way, showing no headers. Therefore you need to rewrite it from the scratch. You may use the default source code for this purpose.
To find the template just search for <ControlTemplate TargetType="{x:Type DataGrid}">
in the given link.

Overlay Grid on top of Image inside ScrollViewer

I have the following markup:
<ScrollViewer>
<StackPanel Orientation=Vertical>
<Image />
<Grid />
<ItemsControl />
</StackPanel>
</ScrollViewer>
Okay so the image is an animated loading gif. When an event is completed the program displays the grid and the items control as well, and the user is able to scroll up and down.
In order to do this currently I just set the image to Visiblity='Collapsed' and the Grid and the ItemsControl to Visible. How can I layer them so that the image is always underneath, and the grid and the items control are on top - so that when I fade out the grid and the items control the image appears underneath?
I know that there is an option for Z-Index, I tried placing the Image, Grid and ItemsControl inside a canvas and setting the Panel.ZIndex property but it didnt seem to work - for one when I set the opacity on the Grid to .5 I couldn't see the image underneath, and also the Grid didn't fill the space either (I need it to stretch to the size of the form).
This should work:
<ScrollViewer>
<Grid>
<Image/>
<StackPanel>
<Grid/>
<ItemsControl/>
</StackPanel>
</Grid>
</ScrollViewer>

How do I wrap ListView and GridView inside ScrollViewer without breaking scrollViewer scrolling

I have this layout:
<ScrollViewer>
<StackPanel>
<ListView>
<ListView.View>
<GridView>
<!-- Content -->
</GridView>
</ListView.View>
</ListView>
<!-- Filtering Controls -->
</StackPanel>
</ScrollViewer>
The screen doesn't have a fixed or max height. The Scrollbar shows fine and works fine, but when the mouse is over the items in the List/GridView, the ScrollViewer doesn't receive the event and doesn't scroll.
I've read about how I can implement an extension, but that seems overdoing it, what's the simplest solution to make the List/GridView either pass the mouse wheel scroll event to the ScrollViewer or ignore them?
This happens because listview handles the scroll event, the simplest I can suggest is to override content of listview and insert a itemsPresenter which has no scroll properties inside it.
I solved such problem by using something like this..
<ListView>
<ListView.Template>
<ControlTemplate>
<ItemsPresenter/>
</ControlTemplate>
</ListView.Template>
</ListView>
Have a look here for detailed discussion on this topic

Navigate properly between tab in a WPF MVVM application

I'm doing a WPF application with the M-V-VM patern (i'm using galasoft if it's relevant), but I have issues when I navigate through a tabcontrol.
I'm adding tabs on the run. All the binding seems to goes well : inside the tab or in the header of the tab.
I've bind my tabcontrol to a observable list. Through an interface I'm adding several types of viewmodel to this list and the binding seems correct.
My XAML code looks like this :
<Grid>
<Grid.Resources>
<DataTemplate x:Key="itemTemplate">
<TechnicalControls:ItemTab />
</DataTemplate>
</Grid.Resources>
<TabControl Grid.Row="1" x:Name="MainTab" Grid.Column="1"
ItemsSource="{Binding TabViewModels}"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
ItemContainerStyleSelector="{StaticResource LastItemStyleSelector}"
ItemTemplate="{StaticResource itemTemplate}"
>
<TabControl.Resources>
<DataTemplate DataType="{x:Type VM:JobViewModel}" x:Shared="False" >
<FunctionnalControls:Job />
</DataTemplate>
<DataTemplate DataType="{x:Type VM:ExcelJobViewModel}" x:Shared="False">
<FunctionnalControls:ExcelJob />
</DataTemplate>
<DataTemplate DataType="{x:Type VM:MonitoringViewModel}" x:Shared="False">
<FunctionnalControls:Monitoring />
</DataTemplate>
<DataTemplate DataType="{x:Type VM:ErrorViewModel}" x:Shared="False">
<FunctionnalControls:Error />
</DataTemplate>
</TabControl.Resources>
</TabControl>
</Grid>
For example if I go from a ExcelJob to another ExcelJob usercontrol, the new usercontrol is not load properly but it changes then it works, for exemple, I can go to a ExcelJob to another ExcelJob if only I go through the monitoring.
I've already look to this this but it didn't work for me.
I've also looked at this : it says that we should not used inputs because you can focus them. I've tried to set the IsEnabled property on the users controls to false. I did it when tabs were changing. It didn't work...
The only solution that I can see is to go through another a new usercontrol with no other purpose to be used every time a tab is changed but this is ugly, and I'm pretty sure, Microsoft thought about this and came up with a better solution.
If necessary I can put the code of the view model.
EDIT : Just to clarify, when I click on other tab with the same control, instead of showing me the new usercontrol, it shows me the previous one. In order to see the new one, I have to change to another tab with another usercontrol then come back on the one I want to see.
I've look through debug and the when I click on the other tab It doesn't call the viewmodel
<UserControl x:Class="App.ExcelJob"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
DataContext="{Binding Main.ExcelJobVM, Source={StaticResource Locator }}">
<Grid >
<Label>Futur Excel Job</Label>
<TextBox Width="200" Height="60" Text="{Binding Header}"/>
</Grid>
</UserControl>
So Main returns the Mainviewmodel and Main.ExcelJobVM returns the good viewmodel of the usercontrol. the returned isntance is based on selected Index.
The only thing I need is to force the redrawing of the usercontrol or recall the method to update the datacontext, by loading the good viewmodel. I tried, I've failed so far. I'm not sure of what I'm doing because I want to use the event SelectionChanged of the tabcontrol but it would be in the code behind, and I don't know if it would still respect the MVVM pattern.
The problem is that you have the DataContext hardcoded in your UserControl, and your UserControl is a Template.
When you switch to a tab that uses the same Template, WPF doesn't bother to redraw the template and only changes the DataContext behind the template. But in your case, you have the DataContext hardcoded in the UserControl, so it's not using the existing data context from the TabItem.
The best solution would be to remove the DataContext binding from your UserControl, and let it be inherited from the TabItem when the selected item changes.
For example:
WPF says
User has selected ExcelJobA for display. Because of the DataTemplate, let me draw it using an ExcelJob UserControl
<TabItem>
<ContentPresenter DataContext="ExcelJobA">
<local:ExcelJob DataContext="{Binding Main.ExcelJobVM, Source={StaticResource Locator }}" />
</ContentPresenter>
</TabItem>
So an ExcelJob UserControl gets created, and by default the DataContext of ExcelJobA would be inherited by the UserControl.
When the user changes the selected tab to ExcelJobB, WPF goes
Hey, the user has changed to ExcelJobB. Because of the DataTemplate, let me draw it using an ExcelJob UserControl, BUT WAIT! I'm already displaying an ExcelJob UserControl, so let me just change the DataContext behind that to ExcelJobB
<TabItem>
<ContentPresenter DataContext="ExcelJobB">
<local:ExcelJob DataContext="{Binding Main.ExcelJobVM, Source={StaticResource Locator }}" />
</ContentPresenter>
</TabItem>
So the actual displayed ExcelJob UserControl does not get recreated or redrawn, but only the DataContext behind it changes.
HOWEVER, because you have hard-coded the DataContext inside your UserControl, the actual data context obtained from the selected item doesn't ever get used, because a DataContext specified from inside a <Tag> always takes precedence over a DataContext that would be inherited from further up the visual tree.
You need to remove the DataContext binding from inside your UserControl, and let it get passed in normally from your TabControl's SelectedItem, and it will work fine.
<TabItem>
<ContentPresenter DataContext="ExcelJobA">
<local:ExcelJob /> <!-- DataContext inherited from the ContentPresenter -->
</ContentPresenter>
</TabItem>

Hide control when another control overlaps it

I am having pretty big problem with windows forms controls hosted in WPF. When, for example, user scrolls the window, the hosted control goes on top of the window, although it should be hidden.
I know this is known problem, and default behavior of hosted controls, but I think it can be solved if control's visibility is somehow binded with: whether other controls overlap it, or not. If other controls are overlapping, it should become Collapsed or Hidden, if not, it should be Visible.
I made some kind of solution for this, but I did it on ScrollChanged event of a ScrollViewer and it works only in special situations. If somebody knows how to achieve that with binding, so it can be applied to any hosted control, please share your ideas.
For this same problem, we implemented something curious...
Windows forms host is unaffected by Z-order so scroll viewer wont be able to partially hide/ clip it for the area which is visible under the scrollviewer.
So we had two options...
Use Windows form host to host rest of the WPF UI in it which means we reverse the ownership of the UI. The WindowsFormsHost must host all the UI in it having a WinForms based scroll viewer which in turn will host the WPF UI.
Implement a scroll offset for calculated height of the windows forms host and when user scrolls add this offset to the scrollviewer's position and hide the windforms host yourself (Visibility = Hidden and NOT Collapsed). This way it gives an effect that you cannot partially scroll a winforms host but that scroll it completely off the scroll viewer. And because winformshost is Hidden (not collapsed) it continues to occupy that much height inside the invisible area under the scroll viewer (thereby maintaining its scroll position).
Let me know if this guides you in correct direction.
You can do a little trick. When you declare an WindowsFormsHost, it's parent is first HWND component. Usually it's root window. So, clip area for controls is whole window.
I'll show an example with WPF ScrollViewer.
<Window>
<Grid>
<ScrollViewer Margin="20,50">
<ItemsControl ItemsSource="{StaticResource StringArray}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WindowsFormsHost>
<wf:Button />
</WindowsFormsHost>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Window>
In this case behaviour will be like you described. Buttons will be out of ScrollViewer bounds.
But there's a way to create "intermediate" HWND item to clip WinForms area over ScrollViewer. Just place another WindowsFormsHost with ElementHost like below:
<Grid>
<WindowsFormsHost Margin="20,50">
<ElementHost x:Name="This is clip container">
<ScrollViewer>
<ItemsControl ItemsSource="{StaticResource StringArray}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WindowsFormsHost>
<wf:Button />
</WindowsFormsHost>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</ElementHost>
</WindowsFormsHost>
</Grid>
Now clip area for buttons is ElementHost and WinForms Buttons will be clipped by it on scrolling.
Also you can create ControlTemplate for ContentContol and reuse it where you need it.
<ControlTemplate x:Key="ClipConteiner" TargetType="{x:Type ContentControl}">
<WindowsFormsHost>
<ElementHost>
<ContentPresenter />
</ElementHost>
</WindowsFormsHost>
</ControlTemplate>
<Grid>
<ContentControl Template="{StaticResource ClipConteiner}" Margin="20,50">
<ScrollViewer>
<ItemsControl ItemsSource="{StaticResource StringArray}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WindowsFormsHost>
<wf:Button />
</WindowsFormsHost>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</ContentControl>
</Grid>

Categories