WPF Control, UserControl, Template confusion - wpf

Disclosure:
I am new to WPF, about a week into it.
Problem:
I am trying to modify the behavior of a GridSplitter, to make it snap to interesting positions, to show a label (that follows the splitter) with current position, to have a context menu driven from said label, etc. I have prototyped all of this successfully on one gridsplitter in one simple test application, with a combination of XAML and some code behind.
Of note is that because the GridSplitter can't host content, I placed the label in the same grid cell as the splitter so that they move together.
So far so good....
Now I wish to replicate my work so that I can use my new GridSplitter functionality in place of the native control in many locations, and furthermore, I wish to have two variants, a horizontal and a vertical. Sounds like inheritance...create a subclass derived from GridSplitter and add in the additional functionality. But all of the reading I have done leaves me wondering how to go about this, and if this is even possible without starting over again and building my own GridSplitter from scratch?
Ideas welcome. Until then I will resume the fetal position.
Thanks

This answer might help you resolve your issue: How to make GridSplitter to "snap" into another element?
By subscribing to the GridSplitterDragCompleted event, you can insert your logic to snap to "interesting" positions.

You should
create a new control derived from GridSplitter.
subscribe to DragCompleted event to implement snapping functionality like DLeh mentioned.
add a few new properties for Label , ContextMenu etc.
supply a style for your new control.

This answers how to place content in the splitter
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="40" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Row 0" Background="Orange"/>
<!--<GridSplitter Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Stretch" Height="20" Background="Purple"/>-->
<GridSplitter Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Stretch">
<GridSplitter.Template>
<ControlTemplate TargetType="{x:Type GridSplitter}">
<TextBlock Text="TextBlock splitter" Background="Yellow" FontWeight="Bold"/>
</ControlTemplate>
</GridSplitter.Template>
</GridSplitter>
<Button Grid.Row="2" Content="Row 0" Background="Salmon"/>
</Grid>

Related

WPF tab control with grid and shared content

I have a wpf tab control in which I would like to have two columns. One column would always show a graph control that will be used to display data depending on the tab selected - but it will always be the same graph control.
Unfortunately, due to the design of the graph control, it is not possible to have more than one graph control mainly because the performance is dismal. I have tried that and it does not work properly.
The other column would show items like combo boxes, radio buttons, etc., that are specific to the selected tab - an example is below
I have also had the tab control in the right-hand column, but the layout of the individual tabs is congested in the right-hand column making for a less than ideal user experience.
Right now, I have the tab control hosted in a grid that has two columns with the column span set to two. For the right-hand pane, I have various group boxes and I control the visibility of those group boxes with triggers using the IsSelected property of the corresponding tab item. This, however, is causing other problems that I have traced to the visibility of the problematic controls.
What I would like to do is modify the template of the control so that I can host all the present controls within the tab control so that the graph control always displays on the left, and that the content of the right-hand tab is controlled by the selected tab.
I figure that doing this will involve either the control template or another template for the tab control, however, I have been unable to find anything like this so far. Is there a way to do something like this and if so, is there a guide to doing so or some hints as to how I might accomplish this?
Thanks.
The way I would approach this requirement would be something like below. And I would apply a template/style to buttons so that they have a look of a TabHeader.
<Grid Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Orientation="Horizontal">
<StackPanel.Resources>
<ControlTemplate x:Key="ButtonTemplate">
<!--Template style-->
</ControlTemplate>
</StackPanel.Resources>
<Button>Root bending</Button>
<Button>S-N curve bending</Button>
<Button>S-N curve contact</Button>
</StackPanel>
<Grid Grid.Row="1" Grid.Column="0">
<!--Your graph control goes here-->
</Grid>
<Grid Grid.Row="1" Grid.Column="1">
<!--Show/hide these based on buttons-->
<!--Control 1 with combo boxes, radio buttons, etc.-->
<!--Control 2 with combo boxes, radio buttons, etc.-->
<!--Control 3 with combo boxes, radio buttons, etc.-->
</Grid>
</Grid>
you can declare ChartControl as a Resource and use it in every Tab.
To confirm that ChartControl is the same, type something in TextBox and then select another Tab. Text stays the same. Initialization time shown in TextBlock stays the same.
<Window x:Class="XamlApp.Window6"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="TabsWindow"
Height="480" Width="640">
<Window.Resources>
<Grid x:Key="IamChartControl" Background="Khaki">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Source={x:Static system:DateTime.Now}}" Margin="5"/>
<TextBox Text="hello" Grid.Row="1" Margin="5"/>
</Grid>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding Source='12345'}">
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ContentControl Content="{StaticResource IamChartControl}"/>
<TextBlock Grid.Column="1" Text="{Binding}"/>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Thanks for the suggestions. Given there is a large amount of existing code and since the graph control is not entirely WPF MVVM compliant, the better answer in this case would be the answer posted by Ritesh. Putting the chart in a resource would have required more of a rewrite of the code than I presently have time to do.
However, I figured out the problem that I was seeing - which was some controls were not showing bold text when I thought that they should be. This was entirely my fault.
On each tab for each field, I had multiple different labels that were made visible depending on the result set that the user chooses. It has been a long time since I visited this code, and what I was doing was adding the bold fontweight because it makes the values stand out better.
Embarrassingly, I forgot that I had implemented it this way.
Instead of the multiple different labels approach, I am going use a single label for each field and set the appropriate content binding in a multidata trigger as that will make the somewhat cleaner. Its a pretty complicated app.
I was going to delete this, but others have asked a similar question, however, I think Ritesh's answer is different than the other cases.

Two components in the same .xaml Grid.Row. When I scroll down, one of them floats, the other scrolls. I need both of them to scroll

I've come accross some .xaml code I need to fix. Currently, it's made of 2 grid components with this layout:
<Grid d:SomeDataContext>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListView Style="{StaticResource SomeListStyle}" Grid.Row="0" Margin="0,0,0,0" Grid.RowSpan="2">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" Margin="0,0,0,80" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<mycontrol:Panel1Control />
<mycontrol:Panel2Control />
<mycontrol:Panel3Control />
<mycontrol:Panel4Control />
<mycontrol:Panel5Control />
</ListView>
<controls:HeaderControlTransparent Grid.Row="0" />
</Grid>
When this xaml is interpreted, in run time you get a nice list scroller made up of Panel1, Panel2, etc... On top of that, there's some transparent header aligned to the top of the screen.
In fact, this header is "SO aligned" it doesn't move when scrolling, but floats.
The desired placement is something like:
[Header]
[Panel1]
[Panel2]
[Panel3]
[Panel4]
[Panel5]
And the desired behaviour when someone scrolls, shoule be the [header] element disappearing from the upper part of the screen as if it was one of the other panels.
Desired (not happening)
...
[Panel3]
[Panel4]
[Panel5]
Undesired (happening)
[Header]
[Panel4]
[Panel5]
Currently the header doesn't scroll and it just floats on top of everything, aligned to the top part of the screen, while the panels scroll.
Any hint I should be addressing? I think everything is in order. I can't see why one row of the grid scrolls and the other doesn't.
I'm kind of new to .xaml so, this may also be one of the reasons of my troubles.
Thanks.
So may as well add a little to the comment for some additional explanation to future readers and apparently some easy points.
In your example there's two rows (for I guess some other reason) of the Grid with both of the children within the same cell. Since the Header control part sits underneath the ListView in the DOM then it's rendered logically on top of the underlying ListView.
A ListView already as a ScrollViewer built into the control template to nest its items in. However the OP requires elements outside of the controls template to scroll with the items in the ListView.
So by Embedding both the ListView and the Header control in the right order within their own parent ScrollViewer, the desired result can be had of scrolling both bits of content.
e.g. (in pseudo)
<Grid>
<ScrollViewer>
<objects2/>
</ScrollViewer>
<object1/>
</Grid>
Would float object 1 over top of objects 2 and keep object 1 stationary while everything else scrolls. However;
<ScrollViewer>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<object1/>
<objects2 Grid.Row="1"/>
</Grid>
</ScrollViewer>
Will scroll all the children keeping their stacked location.
Hope this helps, cheers!

TabControl width increases on adding tabs

My application UI is divided in 2 parts. Left side is Navigation Menu and Right is View Area where the selected Menu content is displayed. Now, one of the menu is reports. I am using Tabcontrol with Header and Content Template. Template has a ViewModel as DataType and content as the respective View which is a UserControl. This TabControl is inside a scrollviewer which is set as horizontal and vertical alignment to stretch.
The user control hosts a ContentPresenter inside a Grid which is bound to a ReportHost which has a reportviewer as child. I am using this ReportViewer to generate reports.
When the user opens a report, it opens in a new tab. It works fine till the number of tabs is such that the tabheaders are contained inside the viewing area. But as soon as more tabs are added, it causes the tabcontrol width to stretch, causing the content area of the tab to stretch and the contentpresenter also stretches causing horizontal scroll to appear.
This finally result in the report to stretch and due to some reason, unknown to me, the report overlaps the Navigation Area of the UI, as if it is not a part of the UI but is overlapping it. The whole report keeps on floating on top of the View Area and Navigation menu on scrolling.
I can fix it by providing the MaxWidth to the ScrollViewer but I don't want to do that. I would like the width of the tabcontroll or the Scrolviewer to be decided purely based on available View Area. How do I do this through the code or XAML without using fixed width.
I am not sure if I was able to explain the situation. Please let me know if more information or clarification is needed. I would be more than happy to provide details.
Edit: Adding Code for information.
<DataTemplate x:Key="TabContent" DataType="{x:Type VM:ReportViewModel}">
<View:Report/>
</DataTemplate>
<DataTemplate x:Key="TabHeader" DataType="{x:Type VM:ReportViewModel}">
<ContentPresenter Content="{Binding Path=TabHeader}"
VerticalAlignment="Center"/>
</DataTemplate>
<ScrollViewer HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap"
Text="Reports" VerticalAlignment="Top" Margin="10,13,0,0"
FontSize="18.667" FontFamily="Segoe UI" Foreground="White"/>
<Border BorderThickness="0" Margin="0,50,0,0"
Background="{DynamicResource Brush_HeaderNew}" Height="50" Width="Auto"
VerticalAlignment="Top"/>
<TabControl ItemsSource="{Binding ReportItems}" Grid.Row="1" Margin="0,20,0,0"
SelectedItem="{Binding SelectedReportItem}"
ContentTemplate="{StaticResource TabContent}"
ItemTemplate="{StaticResource TabHeader}"
/>
</Grid>
</ScrollViewer>
ScrollViewer presents to its child an infinitely large area on which to set itself out, because it reasons that it can just offer scrolling if it's bigger than the space available to ScrollViewer itself. Because yours has scrolling enabled in both directions, that means the TabControl can expand as much as it likes in either direction, and it's not going to be smart enough to know that it's inside a ScrollViewer and that you want the tabs to not take advantage of this virtual space.
From the sound of things, you might want to consider moving the ScrollViewer within the TabControl so that only the contents of the tab is scrollable rather than the whole set. You should be able to do that by modifying the tab content template.

Silverlight 4 - ScrollViewer and child DataGrid MinHeight

edit: I'm rewriting almost the entire question because I realized the question was incorrect and confusing. I apologize for this, but the question had incorrect assumptions that made it impossible to answer. I originally tried to simplify it to make it easier to understand, but this made it impossible to replicate my problem.
If I have an DataGrid with a MinHeight in a ScrollViewer, I would expect that as my ViewPort shrinks, the ActualHeight of the element would be decreased until it hits MinHeight before the scrollbars show up.
Instead, it seems that when the datagrid's rows cumulative heights add up to more than the MinHeight, this value overrides MinHeight
Is there a way to do this without manually sizing everything and having a ton of code?
Example:
<ScrollViewer VerticalScrollBarVisibility="Auto" Background="Red">
<Grid x:Name="LayoutRoot" Background="Black" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition MinHeight="20"/>
<RowDefinition Height="80"/>
</Grid.RowDefinitions>
<sdk:DataGrid AutoGenerateColumns="True" Name="dataGrid1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" MinHeight="20" />
<Rectangle Fill="Blue" Width="100" Height="80" Grid.Row="1" />
</Grid>
</ScrollViewer>
If you were to populate this grid with some rows, if you maximize the window, the grid takes up most of the space and has white space after the rows. If you shrink it down, the layout takes away from the white space until that space runs out, then the root level ScrollViewer kicks in, even though MinHeight has not been reached.
If you replace the DataGrid with another rectangle, the behavior is different (obviously). The new rectangle would shrink down to height 20.
How do I achieve this with the grid? My requirements are to have nested scrollbars on my SL page (which I find distasteful, but it's not in my control). The idea is that the top level scrollbars are a "last resort" of sorts.
What about this:
<ScrollViewer>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="250" />
</Grid.RowDefinitions>
<Rectangle MinHeight="150" Background="Red" Grid.Row="0" />
<Rectangle Height="250" Background="Blue" Grid.Row="1" />
</Grid>
You did not have the Grid.Row values set on either of the rectangles.
You've not provided sufficient information to solve your specific problem. However, it is easy to demonstrate that the ScrollViewer does work in exactly the fashion you desire by distilling down to something as simple as:
<UserControl ...>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Border MinHeight="200" BorderBrush="Blue" BorderThickness="1" Background="Red"/>
</ScrollViewer>
</UserControl>
Put this in a standalone Silverlight application in the main page and you'll see that the ScrollViewer only displays the vertical scroll bar when the window is small enough. You can download the solution here.
This is because ScrollViewer itself has a border and padding that occupies little space of its own. Try considering little extra height that should match space of scrollbar border.
Another option will be to change the control template of scrollviewer and remove the border and extra space occupied around content presenter. And set horizontal scroll visibility to collapsed so it will not occupy space.

GridSplitter is hidden by WinForm element

I am working in a WPF application. And my problem is regarding the GridSplitter visiblity.
In my xaml code,I am maitaining a Grid. In the 3rd row of Grid, I am hosting a Winform DataGridView. In the same row, the GridSplitter is written.
When GridSplitter is dragged to adjust Grid Row sizes, for other controls like Buttons etc it is properly visible.
But when it comes over the DataGridView which I am hosting, the GridSplitter hides behind the hosted control.
In fact, whatever I host instead of Datagridview,makes the GridSplitter hide behind it, when it is dragged.
I tried setting the ZIndex for GridSplitter. It did not make any difference.
Can anyone help me with this?
Following is my XAML sample code:-
<Grid>
<Grid.RowDefinitions>
<RowDefinition Name="rowForButton"/>
<RowDefinition Name="rowForGridSplitter" Height="Auto" MinHeight="81" />
</Grid.RowDefinitions>
<Button Grid.Row="0" Height="50" Width="110" Content="Button in First Row"/>
<my:WindowsFormsHost Panel.ZIndex="0" Grid.Row="1" Margin="30,11,138,0" x:Name="winHost" Height="58" VerticalAlignment="Top" OpacityMask="Transparent">
<win:DataGridView x:Name="dataGridView"></win:DataGridView>
</my:WindowsFormsHost>
<GridSplitter BorderThickness="1" Panel.ZIndex="1" Grid.Row="1" HorizontalAlignment="Stretch" Height="5" ShowsPreview="True" VerticalAlignment="Top">
</GridSplitter>
</Grid>
Thanks.
Unfortuantely the WinForms control will always sit on top of your WPF elements, it does the same when you try and scroll it. The best way to work around it is to put the required logic for sizing/scrolling/whatever the WinForms part into a WinForms control, then host that control in the WPF form.
Your Grid has only 2 rowdefinitions but needs 3. At the moment the WindowsFormsHost and the GridSplitter are sharing the second row (i.e. Grid.Row="1"). Presumably you want the WindowsFormsHost to use Grid.Row="2".

Resources