Custom ListView Control - c#

So, I have been looking for solution more than 12 hours(but without success). How should I change ListView ControlTemplate to get effect like this:
(This question is about this buttons that working like scrollview)
Have you another ideas how to create control like this?

It's vertical representation, but idea is understood: hide scrollbars and manipulate them manually. For more responsive UI you'll need to subscribe to MouseDown event instead of Click, also NullReference exceptions are possible on every line of Grid_Click().
XAML:
<ListView.Template>
<ControlTemplate>
<Grid ButtonBase.Click="Grid_Click">
<Grid.RowDefinitions>
<RowDefinition Height="16"/>
<RowDefinition Height="*"/>
<RowDefinition Height="16"/>
</Grid.RowDefinitions>
<Button Content="^" Grid.Row="0"/>
<Button Content="v" Grid.Row="2"/>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Hidden">
<ItemsPresenter/>
</ScrollViewer>
</Grid>
</ControlTemplate>
</ListView.Template>
Code:
private void Grid_Click(object sender, RoutedEventArgs e) {
bool down = (e.OriginalSource as Button).Content as string == "v";
var scroller = VisualTreeHelper.GetChild((e.OriginalSource as Button).Parent, 2) as ScrollViewer;
scroller.ScrollToVerticalOffset(scroller.VerticalOffset + (down ? 1 : -1));
}
Magical number 2 in GetChild() is index of ScrollViewer inside its parent (Grid).

Related

Making a WPF TextBlock be only grow and not shrink?

In my app I set the Text of the TextBlock named tbkStatus many times.
How can I make the TextBlock be just grow auto to fit the text but not shrink when the text changed?
The StatusText changes every few seconds, There are statuses with long text and short text.
I want my TextBlock to fit itself to the size of the longest text that was, and even when there is a short text the TextBlock should not shrink
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="200" Width="400"
WindowStartupLocation="CenterScreen"
ResizeMode="CanMinimize" Topmost="True">
<Window.Resources>
</Window.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="AUTO" />
<RowDefinition Height="AUTO" />
<RowDefinition Height="AUTO" />
<RowDefinition Height="AUTO" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Please wait ..." Grid.Row="1" Margin="6"/>
<TextBlock Name="tbkStatus" Grid.Row="2" Margin="6" TextWrapping="Wrap" Text="{Binding StatusText}"/>
<ProgressBar Grid.Row="3" Margin="6" Height="20"/>
<Button Grid.Row="4" HorizontalAlignment="Center" Padding="24,3" Margin="6" Content="Stop"/>
</Grid>
</Window>
Xaml only solution:
<TextBlock Text="{Binding StatusText}"
MinWidth="{Binding RelativeSource={RelativeSource Self}, Path=ActualWidth}"
HorizontalAlignment="Left"/>
This way whenever the Width growths, MinWidth growths too. Hence, the control can't shrink back.
I guess you could just listen to Layout Events like SizeChanged or LayoutUpdated or write some sort of behavior
In the below example, the basic premise is to listen to either of these events, and force your control to never shrink
Note this is totally untested and was just an idea, maybe you could set the MinWidth Property instead
Xaml
<TextBlock x:Name="tbkStatus" SizeChanged="OnSizeChanged"/>
Code Behind
private double _lastSize;
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
var textBlock = sender as TextBlock;
if (textBlock == null)
{
return;
}
if (e.WidthChanged)
{
if (textBlock.Width < _lastSize)
{
textBlock.Width = _lastSize;
}
_lastSize = textBlock.Width;
}
}
Also Note
The SizeChangedEventArgs Class has many properties that you might be able to take advantage of
You can do something like this:
<TextBlock Name="tbkStatus" Grid.Row="2" Margin="6" TextWrapping="Wrap" Text="{Binding StatusText}"/>
Since TextBlock doesn't have TextChange event this will do that job
DependencyPropertyDescriptor dp = DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty, typeof(TextBlock));
int textLength =0
dp.AddValueChanged(tbkStatus, (object a, EventArgs b) =>
{
if (textLength < tbkStatus.Text.Length)
{
textLength = tkbStatus.Text.Length;
tbkStatus.Width = textLength * SomeValue; //You have to play around and see what value suits you best since it depends on font and it's size
}
});
Alternatively, you can use a TextBox and make it read only and use the TextChanged event.

WPF ToolbarControl overflow amounts

I'm writing an application that has multiple ToolbarControls. There are other UI elements between the toolbars, so I cannot use ToolbarManagers. Here is the UI Hierarchy:
<UserControl>
<DockPanel>
<StackPanel DockPanel.Dock="Top">
<ToolbarTray> <Toolbar/> <ToolbarTray/>
<Some Other UI Stuff, separators, text, etc/>
<StackPanel/>
<StackPanel DockPanel.Dock="Top">
<ToolbarTray> <Toolbar/> <ToolbarTray/>
<Some Other UI Stuff, separators, text, etc/>
<StackPanel/>
<!-- as the above toolbars expand in size, these two are pushed offscreen>
<StackPanel DockPanel.Dock="Bottom">
<Some Other UI Stuff, separators, text, etc/>
<StackPanel/>
<DockPanel/>
<UserControl/>
My question is, how can I make the Toolbar controls show as many buttons as possible? They overflow into a dropdown menu when full, but currently their MaxHeight values are all hard coded. I am looking to make this dynamic, so the maximum number of buttons appear depending on the size of the monitor. I've tried doing so by removing the height restrictions, but then the last StackPanel always just gets forced off screen.
Notice that the last StackPanel MUST reside on the bottom of the DockPanel, and contains no toolbar but only some other UI elements.
You should place the area with the highest priority first in your dockpanel.
You can test this with the following very primitive setup (place in a WPF window and resize height, observe in which order the controls are expanded / shrinked):
Your current way:
<DockPanel>
<Rectangle DockPanel.Dock="Top" Fill="Green" Height="100"/>
<Rectangle DockPanel.Dock="Bottom" Fill="Red" Height="100"/>
<!-- Last one fills -->
<Rectangle Fill="Orange"/>
</DockPanel>
Let bottom keep its size and shrink top first
<DockPanel>
<Rectangle DockPanel.Dock="Bottom" Fill="Red" Height="100"/>
<Rectangle DockPanel.Dock="Top" Fill="Green" Height="100"/>
<!-- Last one fills -->
<Rectangle Fill="Orange"/>
</DockPanel>
A typical problem with this approach is the tab-order. You can keep the screen visible tab order as follows:
<DockPanel KeyboardNavigation.TabNavigation="Local">
<Rectangle DockPanel.Dock="Bottom" Fill="Red" Height="100" KeyboardNavigation.TabNavigation="Local" KeyboardNavigation.TabIndex="3"/>
<Rectangle DockPanel.Dock="Top" Fill="Green" Height="100" KeyboardNavigation.TabNavigation="Local" KeyboardNavigation.TabIndex="1"/>
<!-- Last one fills -->
<Rectangle Fill="Orange" KeyboardNavigation.TabNavigation="Local" KeyboardNavigation.TabIndex="2"/>
</DockPanel>
I use the rectangles to make different areas and the priority of their resizing visible - you can place any of your controls in place of the rectangles.
Edit: regarding the comment mentioning fixed size and two relative sized areas (I took the freedom to ignore the 20%, 30% and only look at the ratio), a grid might be the better choice:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="Green"/>
<Rectangle Grid.Row="1" Fill="Orange"/>
<Rectangle Grid.Row="2" Fill="Red" Height="100"/>
</Grid>
Regarding rotation: one way would be to swap the row and column definitions... make sure to include all relevant properties in the swap.
<Grid x:Name="myGrid" Margin="0,0,0,40">
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Green"/>
<Rectangle Grid.Row="1" Grid.Column="0" Fill="Orange"/>
<Rectangle Grid.Row="2" Grid.Column="0" Fill="Red" MinHeight="100" MinWidth="100"/>
</Grid>
<Button VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10" Content="Change Rotation" Click="Button_Click"/>
Code behind to swap grid rows and columns:
private void Button_Click(object sender, RoutedEventArgs e)
{
var rd = myGrid.RowDefinitions.ToList();
var cd = myGrid.ColumnDefinitions.ToList();
myGrid.RowDefinitions.Clear();
myGrid.ColumnDefinitions.Clear();
foreach (var item in cd)
{
myGrid.RowDefinitions.Add(new RowDefinition() { Height = item.Width });
}
foreach (var item in rd)
{
myGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = item.Height });
}
foreach (UIElement child in myGrid.Children)
{
var r = child.GetValue(Grid.RowProperty);
child.SetValue(Grid.RowProperty, child.GetValue(Grid.ColumnProperty));
child.SetValue(Grid.ColumnProperty, r);
}
}
It would also be possible to define different data templates for original and rotated view. It all depends on the requirements and complexity of involved controls.
Another alternative idea: lets actually rotate the view including its content... that might help if the parent container is not rotated:
private bool isRotated = false;
private void Button_Click(object sender, RoutedEventArgs e)
{
if (!isRotated)
{
myGrid.LayoutTransform = new RotateTransform(90, myGrid.ActualWidth / 2, myGrid.ActualHeight / 2);
}
else
{
myGrid.LayoutTransform = Transform.Identity;
}
isRotated = !isRotated;
}

WPF shrink other conent when Expander is Expanded

Whenever Expander is expanded I would like to shrink grid cell above containing ListBox, so that you can always access every ListItem(if the listbox's grid cell would not shrink, lowest part would be inacessible). To illustrate:
item item *scrollbar*
item -> item *scrollbar*
item expanderItems
expander expander
I found bunch of threads for resizable expander, but none mentioning resizing other content. The problem is, grid containing listbox in 1st row and expander in 2nd with 2nd row Height set to Auto do not resize itself when expander is expanded.
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
I managed to code an ugly workaround using Expanded/Collapsed events:
private void Expander_Expanded(object sender, System.Windows.RoutedEventArgs e)
{
//grid1.Height is named content of expander
this.LayoutRoot.RowDefinitions[1].Height = new GridLength(this.LayoutRoot.RowDefinitions[1].ActualHeight + grid1.Height, GridUnitType.Pixel);
this.LayoutRoot.RowDefinitions[0].Height = new GridLength(1, GridUnitType.Star);
}
What would be the proper way? Preferably with no code behind and more "automated".
EDIT: xaml
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Expander Header="Expander" Margin="0" Grid.Row="1" VerticalAlignment="Bottom" Height="23" Panel.ZIndex="1" ExpandDirection="Up" Grid.RowSpan="2" Expanded="Expander_Expanded" Collapsed="Expander_Collapsed">
<Grid x:Name="grid1" Background="#FF762CF7" Height="100" Margin="0,-100,0,0"/>
</Expander>
<ListBox Margin="0" Background="#19FFFFFF">
<Button Height="150" Width="100"/>
<Button Height="150" Width="100"/>
<Button Height="150" Width="100"/>
</ListBox>
<Grid Margin="0" Grid.Row="1" Background="#FFAEFFAE"/>
<Grid Margin="0" Background="#FFFFD8D8"/>
</Grid>
The main problem you have is setting the Height property of the Expander. It changes its height when you expand it. But if you are setting it, it has to respect the height you gave it and cannot properly expand.
Instead of setting the Height, I would set the MinHeight (or completely remove height constraints, depending on what you want to do).
Additionally you should remove the Margin of the grid inside the expander.

ScrollViewer is visible?

I'm using ComputedHorizontalScrollBarVisibility but this doesn't work when you set the HorizontalScrollBarVisibility to "Hidden".
What I'm trying to achieve is knowing if a ScrollViewer should be visible but without showing the ScrollViewer. Then bind that result to show the buttons that control the ScrollViewer (in this case the `StackPanel below).
XAML
<ScrollViewer HorizontalScrollBarVisibility="Auto" x:Name="Scroll">
.....
</ScrollViewer>
<StackPanel Visibility="{Binding ElementName=Scroll, Path=ComputedHorizontalScrollBarVisibility}">
<Button Grid.Column="0" Content="Left" HorizontalAlignment="Left" Click="..."/>
<Button Grid.Column="1" Content="Right" HorizontalAlignment="Right" Click="..."/>
</StackPanel >
If you need to control the way the ScrollViewer (or any control, really) is laid out, consider using a ControlTemplate, which is accesible in any Control's Template property. as this will allow you to bind to the object itself and values passed in to it, and provide such templating. This may, however, involve needing to deal with the computations to show the exact part of your control which is visible.
You can get what you want by simple adding up the width of content elements inside ScrollViewer, e.g. if you have a StackPanel (with Orientation=Horizontal) inside ScrollViewer then add up the width of each child element in the StackPanel and compare it with ActualWidth of ScrollViewer. if the sum is less greater than the ActualWidth of ScrollViewer then you need to scroll it.
For more details refer this link
In my experience, the scroll viewer property values can be stale until the next layout pass. It's code-behind in my simple example below but this does work the way you want.
I create a dependency property called "ShowScrollButtons". You can probably watch for extent and viewport size changes and automatically recompute the property.
When the scroll content size changes, I trigger a re-evaluate of ShowScrollButtons. Note the call to UpdateLayout to make sure the extent and viewport sizes are up-to-date. Again, it's a sample so I'm only checking the Width here for left/right scroll buttons
private void UpdateScrollButtonVis()
{
UpdateLayout();
ShowScrollButtons = (Scroll.ExtentHeight > Scroll.ViewportWidth);
}
In XAML...
<Window.Resources>
<BooleanToVisibilityConverter x:Key="boolvis"/>
</Window.Resources>
<Grid x:Name="theGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" Width="100" Height="100" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden" x:Name="Scroll">
<Canvas x:Name="theCanvas" Width="300" Height="300" Background="Green"/>
</ScrollViewer>
<StackPanel Grid.Row="1" Visibility="{Binding ShowScrollButtons,Converter={StaticResource boolvis}}">
<Button Grid.Column="0" Content="Left" HorizontalAlignment="Left" />
<Button Grid.Column="1" Content="Right" HorizontalAlignment="Right" />
</StackPanel >
<Button x:Name="toggle" Grid.Row="2" Height="25" Width="100" Click="toggle_Click">Toggle</Button>
</Grid>
Update:
How about a new approach works with multiple scroll viewers and StackPanels without code-behind.
Use an Attached Property to control the external button visibility:
public class ScrollViewWatcher
{
public static readonly DependencyProperty HorizontalButtonVisibility = DependencyProperty.RegisterAttached(
"HorizontalButtonVisibility",
typeof(Visibility),
typeof(ScrollViewWatcher),
new FrameworkPropertyMetadata(Visibility.Visible,
FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)
);
public static Visibility GetHorizontalButtonVisiblity(UIElement element)
{
return (Visibility)element.GetValue(HorizontalButtonVisibility);
}
public static void SetHorizontalButtonVisibility(UIElement element, Visibility value)
{
element.SetValue(HorizontalButtonVisibility, value);
ScrollViewer sv = element as ScrollViewer;
if (sv != null)
{
sv.ScrollChanged -= sv_ScrollChanged;
sv.ScrollChanged += sv_ScrollChanged;
}
}
static void sv_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var sv = sender as ScrollViewer;
if (sv != null)
{
var vis = sv.ExtentHeight > sv.ViewportWidth ? Visibility.Visible : Visibility.Hidden;
sv.SetValue(HorizontalButtonVisibility, vis);
}
}
}
Then in XAML, bind to the appropriate ScrollViewer like this:
<ScrollViewer
x:Name="sv1" local:ScrollViewWatcher.HorizontalButtonVisibility="Visible"
Grid.Row="0" Width="100" Height="100" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden" >
<Canvas x:Name="theCanvas" Width="300" Height="300" Background="Green"/>
</ScrollViewer>
<StackPanel Grid.Row="1" Visibility="{Binding ElementName=sv1,Path=(local:ScrollViewWatcher.HorizontalButtonVisibility), Mode=OneWay}">
<Button Grid.Column="0" Content="Left" HorizontalAlignment="Left" />
<Button Grid.Column="1" Content="Right" HorizontalAlignment="Right" />
</StackPanel >
This works great in my test. This was a fun challenge. Maybe someone can enlighten us with a better approach but I'm pretty happy with this.
Thank you all for the answers but finally got a workaround a bit easier, instead of binding the visibility from the StackPanel of the buttons just call a ScrollChanged in the ScrollViewer and then in code check for the ComputedHorizontalScrollBarVisibility and change visibilities depending on the result.
XAML
<ScrollViewer HorizontalScrollBarVisibility="Auto" x:Name="Scroll" ScrollChanged="Scroll_ScrollChanged">
.....
</ScrollViewer>
<StackPanel x:Name="BPanel" Visibility="Hidden">
<Button Grid.Column="0" Content="Left" HorizontalAlignment="Left" Click="..."/>
<Button Grid.Column="1" Content="Right" HorizontalAlignment="Right" Click="..."/>
</StackPanel >
C#
private void Scroll_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ScrollViewer scroll = (ScrollViewer)sender;
if(scroll.HorizontalScrollBarVisibility == ScrollBarVisibility.Auto)
{
if (scroll.ComputedHorizontalScrollBarVisibility == Visibility.Visible)
{
scroll.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
BPanel.Visibility = Visibility.Visible;
}
}
}

How to add arbitrary content within a user control

I have a Windows Phone App where I would like to achieve the following:
Define a (user)control which has a Button and an arbitrary control. Once the user clicks the Button, the second control becomes visible and the button is hidden.
I can achieve this directly in the CodeBehind or ViewModel but I would like to have some kind of control that I can reuse on all places where I need this.
I googled a bit and come accross the ContentPresenter.
Control:
<StackPanel Background="Red" x:Name="LayoutRoot">
<Button Content="Dummy"/>
<ContentPresenter/>
</StackPanel>
XAML call:
<controls:MyControl>
<TextBlock Text="Text Content"/>
</controls:MyControl>
But the Contentpresenter cannot be used like this and the Button will never be displayed because the page XAML definition of the content will overwrite the content defined in the user control xaml.
I then found hints to use templates, but I did not yet understand how I could combine this with the logic that I would like to apply here.
Should I create a userControl that has VisibilityProperties for the Button and the other control which are then used by the Template?
Any advice or hint is apprechiated to get an idea where to continue my search.
How about:
Control:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Button Content="ClickMe" Click="Button_Click"/>
<StackPanel Grid.Row="1" x:FieldModifier="public" x:Name="Content"/>
</Grid>
Control.cs:
private void Button_Click(object sender, RoutedEventArgs e)
{
if (Content.Visibility == Visibility.Collapsed)
{
Content.Visibility = Visibility.Visible;
}
else
{
Content.Visibility = Visibility.Collapsed;
}
}
MainPage.xaml:
<local:MyUserControl1 x:Name="MyControl" HorizontalAlignment="Left" Margin="327,124,0,0" VerticalAlignment="Top"/>
MainPage.xaml.cs (adding content):
MyControl.Content.Children.Add(new Button());
MyControl.Content.Children.Add(new Button());
MyControl.Content.Children.Add(new Button());
MyControl.Content.Children.Add(new Button());
MyControl.Content.Children.Add(new Button());
MyControl.Content.Children.Add(new Button());

Categories

Resources