How to get synchronize the two scrollbars in WPF - wpf

I'm learning WPF these days.
I made some XAML like below.
<TextBox x:Name="TxtHex" IsReadOnly="True" MinWidth="500"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
Grid.Column="1"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Visible"
Style="{StaticResource Textbox}"/>
<ScrollViewer Grid.Column="2">
<TextBox x:Name="TxtAnsi" Margin="0,0,5,0" IsReadOnly="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
Style="{StaticResource Textbox}"/>
</ScrollViewer>
I made two TextBoxes with VerticlaScrollBar and ScrollViewer.
I want this two scrollbar to have the same value when one scollbar changed value.
I mean, I want to synchronize the two scroll bars.
So, I want to get value of scollbar and set value to scrollbar.
I've been searching about this, But I can't get any information.
Please give me some tips if someone has good idea.
Thank you.

First, set your XAML code(note: you can change the background)
<TextBox ScrollBar.Scroll="TxtHex_Scroll" AcceptsReturn="True" x:Name="TxtHex"
IsReadOnly="True" MinWidth="500"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Visible" Margin="22,231,271.6,10"
Background="#FFE2E2E2"
/>
<ScrollViewer ScrollBar.Scroll="Scroll_Scroll" x:Name="scroll"
Margin="22,47,271.6,194">
<TextBox x:Name="TxtAnsi" IsReadOnly="True" AcceptsReturn="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
Background="#FFF7EEEE"
/>
</ScrollViewer>
Then, go to code:
private void TxtHex_Scroll(object sender, System.Windows.Controls.Primitives.ScrollEventArgs e)
{
scroll.ScrollToVerticalOffset(TxtHex.VerticalOffset);
scroll.UpdateLayout();
}
private void Scroll_Scroll(object sender, System.Windows.Controls.Primitives.ScrollEventArgs e)
{
TxtHex.ScrollToVerticalOffset(scroll.VerticalOffset);
scroll.UpdateLayout();
}
Do not forget to add your text inside it. So, when you scroll a one, the other one will scroll in the same value. But both of them need enough text to scroll.

Related

WPF Expander and Window resizing after collapse?

I am dealing with one problem regarding to main window resizing after the expander is collapsed and window was manually resized to bigger size by drag/drop before that. The expander is collapsed but the main window is not resized to "fit" the inner component size.
The components are placed to auto sized grid columns and main window is set to WidhtAndHeight size content.
Main window property set:
SizeToContent="WidthAndHeight"
I have done solution via code behind and event handlers (on collapse and expand events). But I think the WPF has better approach ...
Expander XAML implementation:
<Expander Name="expander" Grid.Column="2" Grid.Row="0" Grid.RowSpan="5" ExpandDirection="Right" ToolTip="Expand window for history details" Expanded="Expander_Expanded" Collapsed="Expander_Collapsed" SizeChanged="expander_SizeChanged" >
<Expander.Header>
<TextBlock RenderTransformOrigin="0.3,1.5">
<TextBlock.RenderTransform>
<TransformGroup>
<RotateTransform Angle="90" />
</TransformGroup>
</TextBlock.RenderTransform>
Show history
</TextBlock>
</Expander.Header>
<StackPanel>
<ListView ItemsSource="{Binding Path=HistoryList}" ScrollViewer.CanContentScroll="True" ScrollViewer.HorizontalScrollBarVisibility="Auto" MaxHeight="350" >
<ListView.View>
<GridView>
<GridViewColumn Header="Operation" DisplayMemberBinding="{Binding Operation}" />
<GridViewColumn Header="Result" DisplayMemberBinding="{Binding Result}" />
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Expander>
Does anyone have a clue how to intelligent solve this case?
Thanks for advise.
The SizeToContent property seems to lose its value as soon as you resize the Window manually, to fix this just subscribe to the OnSizeChanged event of your Window, and reset the SizeToContent property:
XAML:
<Window SizeToContent="WidthAndHeight" SizeChanged="MainWindow_OnSizeChanged">
Code-Behind:
private void MainWindow_OnSizeChanged(object sender, SizeChangedEventArgs e)
{
SizeToContent = SizeToContent.WidthAndHeight;
}

Stop WPF TextBox from growing

I have spent two hours researching how to avoid that my WPF TextBox Control grows when a long text has been typed in, but I have not been able to do it, even I have read some answers about it like these ones:
stopping-wpf-textbox-from-growing-with-text
wpf-allow-textbox-to-be-resized-but-not-to-grow-on-user-input
wpf-how-to-stop-textbox-from-autosizing
My code is the following:
<Grid>
<TextBox Margin="6,6,8,28" Name="textBox1" AcceptsTab="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" AcceptsReturn="True"/>
<CheckBox Content="Case sensitive" HorizontalAlignment="Left" Margin="7,0,0,2" Name="checkBox1" Height="16" VerticalAlignment="Bottom" />
</Grid>
One thing that I tried was:
MaxWidth={Binding ElementName=MyGrid, Path=ActualWidth}
But it did not work for me.
I also tried to add the following property to the Grid:
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
Or
<Border x:Name="b" Grid.Column="1"/>
<TextBox Width="{Binding ActualWidth, ElementName=b}" ....... />
But it did not work either.
I need my control to grow when the user stretches the window, but not to when a long text is inserted.
Do you have a different answer for this problem that I might try?
UPDATE!
I have noticed something very strange: If I stretch manually the window, the textbox stops growing when a long text is inserted. That's fine. But I need to achieve this without having to stretch the window every time I run the program
Remove TextBox border and put it into ScrollViewer.
Try to do next:
<ScrollViewer MaxHeight={Binding ElementName=MyGrid, Path=ActualWidth} BorderThickness="0">
<TextBox Margin="6,6,8,28" Name="textBox1" AcceptsTab="True" TextWrapping="Wrap" AcceptsReturn="True"/>
</ScrollViewer>

Constraining a ListView's width to parent to force child text to wrap

I'm trying to get a window similar to a chat window, where a list of text items is drawn. The window should be resizable and each text item should wrap if it does not fit on one line.
What I have so far:
MessageItem - A user control, multiline TextBlock in a Border
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border BorderBrush="Silver" BorderThickness="1" Height="Auto" HorizontalAlignment="Left" Margin="0,10,0,10" Name="messageContainer" VerticalAlignment="Top" Width="Auto">
<TextBlock Height="Auto" HorizontalAlignment="Stretch" Margin="0" Name="messageContent" VerticalAlignment="Stretch" Width="Auto" Text="This is some longer text. Wow that wasn't as long as I thought." TextWrapping="Wrap" Padding="10" />
</Border>
</Grid>
MessageBox - A user control with a ListView that holds MessageItems
<Grid Name="messageGrid" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<!--<StackPanel Height="Auto" HorizontalAlignment="Stretch" Margin="0" Name="messagePanel" VerticalAlignment="Stretch" Width="Auto">
</StackPanel> -->
<ListView HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<local:MessageItem></local:MessageItem>
</ListView>
</ScrollViewer>
</Grid>
The Problem:
If I use the commented out StackPanel for holding MessageItems, It will shrink the MessageItem (and cause the text to wrap) correctly. If I use the ListView, it does not shrink.
I've more or less figured out why from research, but I haven't been able to figure out how to get around it. As far as I can tell I need to override MeasureOverride and/or ArrangeOverride, but I'm far too new to WPF to know WTF I'm doing. (rimshot)
I'm not sure why you're putting the ListView inside a ScrollViewer since the ListView has its own ScrollView internally.
In order to get your MessageItems to wrap you need to turn off any horizontal scrollbars, otherwise the container (ListView or ScrollViewer) will give the MessageItem as much space as it requires and show a scrollbar.
Try
<ScrollViewer ... ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
and
<ListView ... ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
although I'm not sure you even need the ScrollViewer.
You would probably be better off using a ListBox and an ItemTemplate instead of the ListView and user control

ListBox inside ListBox, how do I know which button is clicked?

I have two ListBoxes, one inside another. And both ListBoxes will have items dynamically added into them upon user request.
Each time a button (not shown in the below code snippet) is clicked, a new item is added to the listbox. Each new item includes a new listbox and others.
And I have buttons inside the inner listbox, one for each list item of the inner listbox.
With DataContext, I can find the data binded to the inner list item, and make changes to it, so that changes are reflected on the proper list item.
However, I also need to make changes to the data binded to the outer list item, which corresponds to the button. How can I know which one it is?
I have came up with a solution, which I believe it not elegant enough. By making changes to the model, I can have each inner data holds a reference to the outer data, so that I can find the data binded to the outer list item. This doesn't seem like a proper solution though. Do you have any suggestions?
Below is code snippet of the xaml. I've simplified it, hope it's easy to understand. I feel you don't have to read the whole code.
<ListBox Name="QuestionsListBox" HorizontalAlignment="Stretch" ItemContainerStyle="{StaticResource ListItem}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox Text="{Binding Question, Mode=TwoWay}" Grid.Row="0" HorizontalAlignment="Stretch" TextWrapping="Wrap"/>
<ListBox Name="ChoicesListBox" ItemsSource="{Binding Choices}" Grid.Row="1" HorizontalAlignment="Stretch" ItemContainerStyle="{StaticResource ListItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Click="ChoiceAddButton_Click" Height="72" Width="72" HorizontalAlignment="Left" BorderBrush="Transparent">
<Button.Background>
<ImageBrush ImageSource="/Images/choices.add.png" Stretch="Fill" />
</Button.Background>
</Button>
<TextBox Text="{Binding Value, Mode=TwoWay}" HorizontalAlignment="Stretch" Grid.Column="1" Margin="-20,0" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Why not just use QuestionsListBox.DataContext inside ChoiceAddButton_Click directly? You have a direct way to reference the outer ListBox from your code behind since you've given it a name, and DataContext is an accessible property.
private void ChoiceAddButton_Click(object sender, RoutedEventArgs e)
{
...
var outerLBDataContext= QuestionsListBox.DataContext;
...
}
This works fine for me in a demo solution using your provided XAML.
Edit 2:
Sorry, wasn't thinking. The Button's DataContext will be a Choice, not the Choices collection.
Your inner ListBox's DataContext is not a Question, it's Choices. Your outer TextBox has Question.Question as its DataContext. Binding Text or ItemsSource makes the DataContext point to the binding target. Here is a bit of tricky XAML to sneak in a DataContext reference.
Add an ElementName to your outer TextBox:
<TextBox Text="{Binding Question, Mode=TwoWay}" Grid.Row="0" HorizontalAlignment="Stretch" TextWrapping="Wrap" ElementName="questionTextBox"/>
Now, add a hidden TextBlock inside your inner ListBox:
<ListBox Name="ChoicesListBox" ItemsSource="{Binding Choices}" Grid.Row="1" HorizontalAlignment="Stretch" ItemContainerStyle="{StaticResource ListItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Click="ChoiceAddButton_Click" Height="72" Width="72" HorizontalAlignment="Left" BorderBrush="Transparent">
<Button.Background>
<ImageBrush ImageSource="/Images/choices.add.png" Stretch="Fill" />
</Button.Background>
</Button>
<TextBox Text="{Binding Value, Mode=TwoWay}" HorizontalAlignment="Stretch" Grid.Column="1" Margin="-20,0" />
<TextBlock Name="hiddenTextBlock" Visibility="Collapsed" DataContext="{Binding ElementName=questionTextBox, Path=DataContext}"
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Finally, inside your event handler, you can navigate around the tree to get that reference:
private void ChoiceAddButton_Click(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
if(btn == null) return; //won't happen when this method handles the event
Grid g = btn.Parent as Grid;
if(g!=null) // also unlikely to fail
{
TextBlock tb = g.FindName("hiddenTextBlock") as TextBlock;
if(tb!=null) // also unlikely to fail, but never hurts to be cautious
{
var currentQuestion = tb.DataContext;
// now you've got the DC you want
}
}
}
I'd like to note that this isn't really an elegant solution. It is an all UI solution, however, which could be a useful thing. But better design would be to include Parent references in your Choice and ChoiceList (or whatever they're called) classes and use that. Then it would be as simple as calling btn.DataContext.Parent.Parent with appropriate type conversions. This way your code becomes easier to read and maintain.
You could add an event to your inner model that your containing datamodel subscribes to before adding it to the 'Choices' collection and pass the relevant information that way.
Is this necessary to use the Button control in your solution ??
If not fixed, then you can use the "Image control" as specified below <Image Source="/Images/choices.add.png" Height="72" Width="72" HorizontalAlignment="Left" Stretch="Fill"/>
If you use the Image control then in combination with this you can add the selection changed event to inner list box ( ChoicesListBox). Then in the Handler you can get the item selected as it comes as parameter with the selection changed event(SelectionChangedEventArgs).
Modify the List box and add the Selection changed event handler as below
<ListBox Name="ChoicesListBox" ItemsSource="{Binding Choices}" Grid.Row="1" HorizontalAlignment="Stretch" ItemContainerStyle="{StaticResource ListItem}" SelectionChanged="Items_SelectionChanged">
in page.xaml.cs you can add the handler and access the item as follows
private void Items_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems[0] != null)
{
//Please use the casting to the Choices items type to make use.
var temp = (ChoicesItemViewModel)e.AddedItems[0];
}
}

ComboBoxItem to full width of ComboBox, when using SelectedIndex, or navigating keyboard?

Following XAML causes the "My stuff" being centered of ComboBox until I open the ComboBox, when it correctly stretches itself.
<ComboBox Height="30" Width="300" HorizontalContentAlignment="Stretch" SelectedIndex="0">
<ComboBoxItem HorizontalContentAlignment="Stretch">
<Border Background="Red">
<TextBlock>My stuff...</TextBlock>
</Border>
</ComboBoxItem>
</ComboBox>
The question is, is it possible to get the ComboBoxItem being stretched even when it's selected using SelectedIndex? Same bug, or feature, happens if SelectedIndex is untouched (-1) and one selects the item using keyboard.
Workaround is probably to open the ComboBox programmatically in the beginning of app, which is rather ugly.
You just need to set the width of your border to the dynamic width of your outercontrol:
E.g.
Width="{Binding ElementName=combox1, Path=ActualWidth}">
Try this:
<ComboBox x:Name="combox1" Height="30" Width="300" HorizontalContentAlignment="Stretch"
SelectedIndex="0">
<ComboBoxItem HorizontalContentAlignment="Stretch">
<Border Background="Red" Width="{Binding ElementName=combox1, Path=ActualWidth}">
<TextBlock>My stuff...</TextBlock>
</Border>
</ComboBoxItem>
</ComboBox>
I see yes - i'm sure there is a way round this. It really depends what the end result is that you want. Do each of your data items have a difference background colour to identify them or is it simply a background colour to your whole combobox which you are trying to achieve.
If it is the latter, try this - and perhaps also remove the highlighted selection colour too, else perhaps the code behind route is correct, in terms of preselecting your first item, as that may be an option.
Example of All over Background colour:
<ComboBox Background="Red" x:Name="combox2" Height="30" HorizontalContentAlignment="Stretch" SelectedIndex="0">
<ComboBoxItem Background="Red" HorizontalContentAlignment="Stretch">
<TextBlock Background="Red">My stuff...</TextBlock>
</ComboBoxItem>
</ComboBox>
Hope this helps! :)

Resources