Synchronize separate ScrollViewer and ScrollBar with MVVM pattern - c#

I have a Grid which consists of a ScrollViewer and a ScrollBar which are separated (i.e. the ScrollBar is not bound to the ScrollViewer)
I want both to be synchronized, for instance if the ScrollViewer's VerticalOffset property changes, I want the value of the ScrollBar to change, and vice-versa. I really insist that both should be separated (because in my real project the ScrollViewer is located on a different View)
<Grid Height="200" Width="400">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="6*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0" Background="Gray" />
<ScrollBar Grid.Column="1" Width="30" />
</Grid>

I don't see that you can do this merely by binding a couple of properties.
I think the way to do this is to create two completely separate ScrollViewers and link them together. The one on the left has its scroll bar visibility properties set to hide its scroll bars so all you see is the view. The one on the right just shows the ScrollBar part. (It's possible this might require you defining a custom control template to achieve that effect but it can be done).
Then link them together in code-behind. Use the ScrollChanged event. For example
https://learn.microsoft.com/en-us/answers/questions/43474/wpf-how-to-make-two-panels-share-the-same-vertical.html

Related

How to prevent a TextBlock with TextTrimming CharacterEllipsis to extend Column with Width Auto

I have the following XAML
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Hello World" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Hello ABC DEF World" TextTrimming="CharacterEllipsis" />
</Grid>
</Window>
This results in the following User Interface:
I would like the TextBlock "Hello ABC DEF World" to trim and not extend the column width.
To have this result:
Edit:
Want to point out
I wouldn't need it to be a Grid it could be any content control (StackPanel, DockPanl, Canvas and so on)
I wouldn't need it to be a TextBlock it could be any control (Run, Label and so on)
If what I desire is possible with a different combination of controls, I am more than willing to give it a try.
What I want in non-code terms:
I want 2 lines of text, the first line of text is the "main" information, and the second line of text is the "minor" information.
The "main" information should stretch fully to show its full text.
The "minor" information should only stretch as far as the main goes and not more, if it is longer it should Trim
I know I could achieve this goal with the following XAML:
<TextBlock ...
x:Name="MainInfo" />
<TextBlock ...
MaxWidth="{Binding Mode=OneWay, Path=ActualWidth, ElementName=MainInfo}" />
But if possible I would like to avoid the Binding on ActualWidth.
I hoped that HorizontalAlignment = Left; would prevent the TextBlock's desire to stretch, but that wasn't the case.
But if possible I would like to not use the Binding on ActualWidth.
Well, you need to define the width contraint somehow. Auto effectively means that the column will grow along with the widest element in it, i.e. the "minor" information TextBlock in this case.
So you should set the Width of the column to the ActualWidth of MainInfo, for example using a binding. Or programmatically. Either way, you have to set it one way or another.
You could achieve this in multiple ways.
Depends on the exact desired purpose.
One notable and easy example would be to use some trickery
You could put the main textblock and a rectangle in a horizontally aligned stackpanel with a row span of 2 and with a higher Z index than the secondary textblock. You set the rectangle to have the height equal with the sum of the two textblocks and the width must be set by you after your desired specifications. The two textblocks must have set a fixed height, and the textblock that is inside the stackpanel with the rectangle must be aligned to the top vertically inside the stackpanel. The result, if implemented correctly, should be a rectangle that is covering the part of the secondary textblock that is making the secondary textblock bigger than the main textblock.

How can I make a ScrollViewer properly size dynamically with a StackPanel?

For reference, this is a chat application. This should give you some idea of a final goal.
Additionally, I am very new to WPF. This is one of my first applications and I am making this as a proof of concept. I've been using Windows Forms up until this point, so any comparison or reference to it would help me understand a bit better.
So, the issue at hand:
The chat box for my chat application is a StackPanel (should it be?) which is programmatically populated with TextBlock elements. I need to find a way to scroll down this StackPanel once the available space runs out. I also need it to automatically scroll to the bottom (like a chat would; you wouldn't be able to see the most recent message otherwise).
The question: How can I make a ScrollViewer properly size dynamically with a StackPanel?
Additionally, I also need this StackPanel to size dynamically as the window is sized. This, in turn, would affect the scroll bar.
My current "solution" is to use a ScrollViewer with the StackPanel nested. However, the ScrollViewer and StackPanel do not size properly with a change in window size, as shown in screenshot #2. The XAML code and a screenshot of the designer is shown below.
<Window x:Name="Main" x:Class="dprCxUiDemoWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:dprCxUiDemoWpf"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Background="#FF171717">
<TextBox x:Name="ChatBox" TextWrapping="Wrap" Background="#FF4F4F4F" Foreground="White" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" RenderTransformOrigin="-0.118,12.093" Margin="146,0,0,1" VerticalAlignment="Bottom" Height="46" BorderBrush="#FFFF00F3" KeyDown="ChatBox_KeyDown"/>
<Image x:Name="DprLogo" Source="/dprCxUiDemoWpf;component/Images/logo1.png" HorizontalAlignment="Left" Height="60" Margin="10,0,0,10" VerticalAlignment="Bottom" Width="123"/>
<ScrollViewer Background="Red" Height="Auto" Width="Auto" ScrollViewer.CanContentScroll="True" Margin="146,0,0,0" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" VerticalAlignment="Top" MinHeight="372">
<StackPanel x:Name="ChatPanel" Height="Auto" Width="Auto" Background="DimGray" ScrollViewer.CanContentScroll="True" />
</ScrollViewer>
</Grid>
</Window>
(source: gcurtiss.dev)
Please note the following regarding the first screenshot:
A. The black column (containing the logo) is simply the background color of the window; there is nothing there.
B. The gray portion is ChatBox (the StackPanel)
C. The pink highlighted box below is the text box where messages are entered.
I appreciate and accept any and all help!
You have to use the Grid panel properly. You layout its children by defining rows and columns. Grid is a column/row based layout container. Then configure row and column definitions to control the resize behavior of the cells and their content.
Using absolute positioning and sizes will of course prevent controls from responding to their parent's size changes. Most control stretch to fit the available space. But this requires dimension properties being set to Auto.
You said you are "more of a hands-on learner", but you should still read some documentations. Otherwise you will significantly slow down your progress until stagnation.
There are tons of blogs waiting for you to read them. To poke around in the dark will get you nowhere. You need at least to know the basics. Instead of waiting 13+ hours for a copy & paste ready answer, you could have finished multiple tutorials already and solve this on your own. Success is a good feeling. This is a very trivial problem.
Find yourself a good tutorial that you find easy to understand and start to experiment with the Grid after reading it.
According to your posted code, you obviously have zero idea how this panel works and how you can use it to align your controls.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
<StackPanel />
</ScrollViewer>
<Image Grid.Row="1" Grid.Column="0"
Height="60"
Width="123 />
<TextBox Grid.Row="1" Grid.Column="1" />
</Grid>
I need to find a way to scroll down this StackPanel once the available space runs out. I also need it to automatically scroll to the bottom (like a chat would; you wouldn't be able to see the most recent message otherwise)
You should read about data-binding and MVVM first. Usually you hold an ObservableCollection of items on your VM and bind them to eg a ListBox on your View.
Then you can scroll-down the ListBox, each time a new item got added to your collection:
private void ScrollRecordsListToBottom()
{
if (RecordsList.Items.Count == 0)
return;
var lastItem = RecordsList.Items[RecordsList.Items.Count - 1];
RecordsList.ScrollIntoView(lastItem);
}

How to make a ScrollViewer work without specifying a static height or width?

I'm currently working on UI for a WPF application, and I want to use a ScrollViewer to show the content that might be out of the viewing area of the screen.
I've read all over the internet in the past 2 days, and from what I understand: the ScrollViewer doesn't have knowledge of it's content's/parent's height unless determined statically; thus if there's no specific height written down (in a case of a StackPanel underneath it for example) it won't allow for scrolling.
Now, let's suppose my UI hierarchy looks like this:
<Window> <!--No height here-->
<Grid>
<StackPanel Orientation="Horizontal"/>
<ContentControl>
<UserControl> <!--This user control doesn't have a specific height or width-->
<Grid>
<ScrollViewer>
<Grid /> <!--This grid doesn't contain any StackPanels, or containers with dynamic height, and this is content I want to show-->
</ScrollViewer>
<Grid />
</Grid>
</UserControl>
</ContentControl>
</Grid>
</Window>
I expected the ScrollViewer to scroll appropriately since there's no dynamic container beneath it, but it didn't, and it appeared that setting a static height to it or to the UserControl above it made it work.
But since the app would work on different screen sizes, and all windows are somehow resize-able, I don't want to write static dimensions.
The solution was to use fractional sizes for the grid (which gives scrollviewer height somehow), example:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" /> <!--this gives the second row the space it needs and leaves the scrollviewer with all the remaining space, this will also work if you need to make the second row child fixed-->
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0"/>
<Grid Grid.Row="1"/>
</Grid>
for a better experience with grids, use WpfAutoGrid.

How do I split a WPF window into two parts?

I want to create an app that has a listbox on the left side (I'll style it to make it look good later).
On the right side, I want to have an area where I can add controls, etc
So the question is what do I need to do to split the Window into two unequal parts
(left side approx 350 pixels wide and height should be the entire window) and the remainder is for my "canvas."
You can use a Grid:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="350" /> <!-- Or Auto -->
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" />
<Canvas Grid.Column="1" />
</Grid>
Or you could use a DockPanel:
<DockPanel>
<ListBox DockPanel.Dock="Left" Width="350" />
<Canvas />
</DockPanel>
The benefit of the Grid is you have finer control over the layout and could allow the end user to dynamically resize the columns using a GridSplitter.
An alternate approach to CodeNaked's solution can be to use DockPanel where Canvas takes all space that is left automatically and you don't have to work out splits.
Of course this has the limitation of docking only to the four edges (with possibility of stacking up at edges) but I tend to prefer DockPanel when I'm making initial UI as they're rather quick and easy to setup compared to Grid setup which can get complex fairly quickly.
<DockPanel LastChildFill="True">
<ListBox DockPanel.Dock="Left" Width="350"/>
<Canvas />
</DockPanel>

Silverlight TreeView ScrollBars disappear inside a StackPanel

I'm building a project that loads data from a webservice into a TreeView Control. When the TreeView is contained on the LayoutRoot grid by itself and it's height is set to Auto, if the contents extend beyond the vertical or horizontal limits of the treeview scrollbars appear automatically, as expected.
If that same TreeView control is placed inside a StackPanel, it's behavior changes. When data extends past it's limits, no scrollbar appears and data simply clips off the edge with no access to it. If I manually set the height of the TreeView in this scenario, the scrollbars will appear again as expected.
Clearly there seems to be some interaction between the StackPanel and TreeView that I'm not seeing.
Can anyone explain this and suggest an appropriate way to handle the situation?
Per request in comments:
The following XAML works fine and renders scrollbars as expected (notice the Height on the TreeView is Specified):
<StackPanel >
<controls:TreeView ItemTemplate="{StaticResource MainEntryIndexTemplate}"
x:Name="CodeBookIndexTreeView" Height="500" />
</StackPanel>
The following also displays scrollbars as expected (notice no StackPanel, but the TreeView's Height is set to Auto):
<controls:TreeView ItemTemplate="{StaticResource MainEntryIndexTemplate}"
x:Name="CodeBookIndexTreeView" Height="Auto" />
Finally, this code fails in that the TreeView will not display scrollbars and data scrolls off the bottom and/or right hand side of the control (notice the TreeView is in a StackPanel and the TreeView's Height is set to Auto):
<StackPanel >
<controls:TreeView ItemTemplate="{StaticResource MainEntryIndexTemplate}"
x:Name="CodeBookIndexTreeView" Height="Auto" />
</StackPanel>
Cheers,
Steve
Managed to get this one figured out with some help from the Silverlight.net forums. You can read the original question and answer here. It turns out that a StackPanel oriented vertically (the defaul orientation) will give infinite size to its children, so since the TreeView has infinite size, the scroll bars will never display. This does NOT happen when using a grid as your layout element.
The following XAML will render the TreeView scroll bars as expected. (Notice that the TreeView is contained in row 2 of a Grid, not a StackPanel and that the Height of the TreeView is set to Auto.) There are some additional controls in this code snippet because that was the reason for the extra container in the first place:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<TextBlock Text="Enter Search Term" />
<TextBox x:Name="SearchTermTextBox" Width="200" KeyUp="SearchTermTextBox_KeyUp"/>
</StackPanel>
<Button x:Name="SearchButton" Content="Search" Click="SearchButton_Click" Width="100"
HorizontalAlignment="Left" Grid.Row="1" />
<controls:TreeView ItemTemplate="{StaticResource MainEntryIndexTemplate}"
x:Name="CodeBookIndexTreeView" Height="Auto" Grid.Row="2" />
</Grid>
Yeh - StackPanels don't seem to be a great control for containing variable sized objects.
Similarly; I have had trouble with DataTemplates composed of textblocks within a stackpanel, used as ListItem templates within a ListBox. Intention was for the text block to wrap, but instead it just continues beyond the bounds of the list box, for which there is no horizontal scroll. Best I've managed so far was to fix the width of the Grid containing the stackpanels but not yet found a satisfactory solution.. would love to know if there is one!

Categories