How to approach for this kind of UI in WPF? - wpf

I have two panel , Left Hand site represents the list of options or menu and right hand side will be list of usercontrol assigned to eatch menu items in the left as Listbox or Items control.
The requirement is
eg. If i move the thumb of the scrollbar in the right hand side panel to anyway near the usercontrol2 , the Usercontrol 2 heading in the heading panel should get activated and if iam moving the thumb to the usercontrol1, the usercontrol 1 heading in the heading panel should get activated and so on.
So how to proceed to accomplish these kind of UI.? Any suggestion is greatly appreciated?
The basic idea is to reduce the no of clicks in the Heading Panel. Right hand side is heavily packed with UI elements so user wants to avoid unnecessary click in the heading.
User will not click on the Left side heading panel. While traversing the right hand panel's scrollviewer the heading should automatically get selected to give the user about the control which he is entering or using now.

Following should work:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border Grid.Column="0">
<ItemsControl>
<!--List on Left : List of Usercontrols-->
</ItemsControl>
</Border>
<Border Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Visible"
HorizontalScrollBarVisibility="Disabled">
<ItemsControl>
<!--List on Right : List of Usercontrols-->
</ItemsControl>
</ScrollViewer>
</Border>
</Grid>
Use Template Selectors to select which UserControl to display in lists.
EDIT-
You could try something like following:
XAML:
<Window x:Class="WpfApplication1.Window4"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window4"
Height="300"
Width="300">
<Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0">
<ListBox Name="ListBox1"
ItemsSource="{Binding}"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Height="50"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="5"
Padding="3">
<TextBlock Text="{Binding}" />
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
<Border Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Visible"
HorizontalScrollBarVisibility="Disabled"
ScrollChanged="OnScrollChanged"
Name="ScrollViewer1">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Height="250"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="5"
Padding="3">
<TextBlock Text="{Binding}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
</Grid>
</Grid>
</Window>
Code:
public partial class Window4 : Window
{
public Window4()
{
InitializeComponent();
DataContext = Enumerable.Range(1, 25);
}
private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
{
var element = ScrollViewer1.InputHitTest(new Point(5, 5));
if (element != null)
{
if (element is FrameworkElement)
{
ListBox1.SelectedItem = (element as FrameworkElement).DataContext;
}
}
}
}
NOTE:
This is just a sample code. Just one of possible ways to do it. And it is not a very healthy piece of code. Some refactoring might be needed. I would wrap this logic up in an Attached Property or a Behavior.

I would use a Scrollbar control and use it somehow like an up/down button. If you move the scroll up you go to the next control and the same moving down.
Not sure if you know what I mean, let me know.

Related

Expandable ListView's height exceed the window height

I have a user control ListView1.xaml which looks something like this:
<Grid>
<ListView ItemSource="{Binding}">
...
</ListView>
</Grid>
In my window I use this control 3 times. Something like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Expander Grid.Row="0" VerticalAlignment="Top">
<local:ListView1 DataContext="{Binding Source1}"/>
</Expander>
<Expander Grid.Row="1" VerticalAlignment="Top">
<local:ListView1 DataContext="{Binding Source2}"/>
</Expander>
<Expander Grid.Row="2" VerticalAlignment="Top">
<local:ListView1 DataContext="{Binding Source3}"/>
</Expander>
</Grid>
When I set Height="*" for each Grid.Row, it would divide the space into 3 equally. It is taking empty space even when the first 2 tabs are not expanded:
If I set Height="auto", it would have what I am looking for: when a tab is collapsed, only the expanded one is taking place:
But one problem with Height="auto" is that there is no scrollbar on the ListView because the height of the ListView is expanding beyond the height of the window.
How would I be able to keep the expanders behave that way and have scrollbar for each ListView when the content is larger that the window?
I would consider a tabcontrol for this sort of UI.
Part of the problem is the grid isn't sizing to it's parent when all it's rows are auto.
If you change from using a grid to a dockpanel then you will get scrollbars.
When you expand the second expander then you'll have to decide what you want to happen though. You'd have to close the first or recalculate what size you want each to be in code.
But I think this is a lot closer to usable.
My minimal reproduction uses listboxes.
<DockPanel>
<Expander DockPanel.Dock="Top">
<ListBox ItemsSource="{Binding IntList}"/>
</Expander>
<Expander DockPanel.Dock="Top">
<ListBox ItemsSource="{Binding IntList}"/>
</Expander>
<Expander DockPanel.Dock="Top">
<ListBox ItemsSource="{Binding IntList}"/>
</Expander>
</DockPanel>
And my viewmodel:
public partial class MainWindowViewModel : ObservableObject
{
public List<int> IntList { get; set; } = new();
public MainWindowViewModel()
{
for (int i = 0; i < 300; i++)
{
IntList.Add(i);
}
}

How to align buttons horizontally and spread equally with xaml windows phone

I have an ObservableCollection which contains ViewModel which in turns defines my buttons definitions.
I've been at it for hours, reading articles after articles but to no avail. I've tried using a Listbox and this is the closest I've got to. My buttons are getting build horizontally but assuming I'm displaying 3 buttons, I want one displayed on the left, one displayed in the middle and one displayed on the right.
<ListBox Grid.Row="2" ItemsSource="{Binding Links}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<StackPanel Background="Beige" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Button Grid.Column="{Binding Column}"
Grid.Row="0"
Width="90"
Height="90">
<ContentControl>
<StackPanel Orientation="Vertical">
<Image Source="{Binding Image}" Width="36" Height="36"
Margin="5" Stretch="UniformToFill"
HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Description}"
Foreground="#0F558E"
FontSize="18"
HorizontalAlignment="Center"/>
</StackPanel>
</ContentControl>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
As you can see, I set the column dynamically using a property from my ViewModel but I no longer have a grid as I couldn't get it to work, but ideally I'd like to use a grid so that I can specify in which Column to set the button.
When I use a StackPanel, it works but the buttons are right next to each other rather than being split evenly through the entire width of the screen.
I've done something similar to the above using ItemsControl and using a grid, and I can see each button getting added but they overlap each other in row 0, col 0. If I bind the row to the Column property for testing purposes, it build it correctly but my buttons are displayed on different rows which is not what I want. I want each button to be aligned horizontally.
How can I achieve this? I know I could just hard code 3 buttons and just change their icons and text and handle the relevant code by passing the relevant button as binded parameter, but ideally I'd like to build the buttons dynamically in a grid and position them using the column.
Note that the number of column would be fixed i.e. 3, as I'll never have more than this.
Thanks.
but ideally I'd like to use a grid so that I can specify in which
Column to set the button.
In any Xaml variant, why not just use that Grid, such as shown below, where the Grid is set to consumes all the horizontal space.
Then with the grid's center column to be star sized and to have the rest of the remaining space be consumed after button 1 and button 3, which auto size into their own spaces:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0"/>
<Button Grid.Column="1" HorizontalAlignment="Center"/>
<Button Grid.Column="2"/>
</Grid>
If that fails, set button one's HorizontalAlignment to be Left and button three's as Right.
As an aside with the list box, it may not be stretching to the whole horizontal size of the screen. Check out my answer to a WP8 sizing issue:
ListBoxItem HorizontalContentAlignment.
I eventually found the answer to my problem in an article I found on the web.
You can check it out here: Using Grids with ItemsControl in XAML
In short, you need to subclass the itemsControl and you need overwrite the GetContainerForItemOverride method which will take care of copying the "data" of the ItemTemplate to the ContentPresenter. In this instance, the row and column, but for my requirement, it is just the Column, since my row will always be 0.
Here is core part of the code if you don't want to check the full article which resolve the problem of setting controls horizontally in a grid using ItemsControl but note the article takes care of creating rows & columns dynamically as well, which I'm not interested in for my project.
public class GridAwareItemsControl : ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
ContentPresenter container = (ContentPresenter)base.GetContainerForItemOverride();
if (ItemTemplate == null)
{
return container;
}
FrameworkElement content = (FrameworkElement)ItemTemplate.LoadContent();
BindingExpression rowBinding = content.GetBindingExpression(Grid.RowProperty);
BindingExpression columnBinding = content.GetBindingExpression(Grid.ColumnProperty);
if (rowBinding != null)
{
container.SetBinding(Grid.RowProperty, rowBinding.ParentBinding);
}
if (columnBinding != null)
{
container.SetBinding(Grid.ColumnProperty, columnBinding.ParentBinding);
}
return container;
}
}
The final xaml looks like this:
<controls:GridAwareItemsControl ItemsSource="{Binding Links}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid Background="Pink">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Grid.Column="{Binding Column}"
Grid.Row="0"
Width="120" Height="120">
<ContentControl>
<StackPanel Orientation="Vertical">
<Image Source="{Binding Image}" Width="36" Height="36" Margin="5"
Stretch="UniformToFill" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Description}" Foreground="#0F558E"
FontSize="18" HorizontalAlignment="Center"/>
</StackPanel>
</ContentControl>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</controls:GridAwareItemsControl>
Once I used the new control, my buttons were correctly placed inside the grid, and therefore were evenly spaced out as the grid took care of that wit the ColumnDefinitions.
If anyone knows how to achieve the same without having to create a new control and overriding the method (in other words, pure XAML), please post it as I'd be very interested to see how this can be done.
Thanks and thank you to Robert Garfoot for sharing this great code!
PS: Note that I've simplified my xaml in order to create a test sample without any style on the buttons, so these are rather large if you try based on this sample.
UPDATE:
Small typo correction but my grid column definition was defined as
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
but as #OmegaMan suggested, to be evenly spread, it should have been defined as
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
I was able to accomplish this with a stackpanel inside of a grid, avoiding columns altogether. If you set the stackepanel's HorizontalAlignment to "center", it will center itself inside the grid and grow as buttons are added, still staying centered inside of the grid. Then it's just a matter of margins to have the buttons equally spaced:
<Grid>
<StackPanel
VerticalAlignment="Stretch"
HorizontalAlignment="Center"
Orientation="Horizontal"
>
<Button HorizontalAlignment="Center" Content="Add" Width="104" Height="32" Margin="24,0"/>
<Button HorizontalAlignment="Center" Content="Edit" Width="104" Height="32" Margin="24,0"/>
<Button HorizontalAlignment="Center" Content="Remove" Width="104" Height="32" Margin="24,0"/>
</StackPanel></Grid>

Creating a sidebar - flyout like Windows desktop app in WPF

what i am trying to do is create a Desktop application in WPF whose UI is such that a small icon will remain fixed in the center of the left edge of screen and on click(or maybe hover) will slide open a sidebar(like the google desktop bar) running along the left edge of the screen (fixed position, cannot be moved).
do note that what i'm asking for might be like an appbar but i do not want the desktop icons along the left edge to be moved as it happens with an appbar i.e. i do not want it to hog up the desktop spacce....can anyone please suggest me a way out ??
I have implemented a partial solution using this, but i cant get the slide animation and fixed position to workout
Something like this could work:
then of course you could create a slide in animation for the sidebar. This shows (partial) transparency and the switching principle.
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStyle="None" Topmost="True" WindowState="Maximized"
AllowsTransparency="True" Background="Transparent">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Rectangle Name="rect" Width="100" VerticalAlignment="Stretch" Fill="#99000000" Visibility="Collapsed" />
<Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" Width="32" Height="32" FontSize="16" VerticalAlignment="Center" HorizontalAlignment="Right" Background="White" Click="Button_Click">></Button>
</Grid>
</Window>
C#:
private void Button_Click(object sender, RoutedEventArgs e)
{
if (rect.Visibility == System.Windows.Visibility.Collapsed)
{
rect.Visibility = System.Windows.Visibility.Visible;
(sender as Button).Content = "<";
}
else
{
rect.Visibility = System.Windows.Visibility.Collapsed;
(sender as Button).Content = ">";
}
}
Based on this answer and more answers on this site I made a side bar, I liked the result so i made a repo.
https://github.com/beto-rodriguez/MaterialMenu
you can install it from nuget too.
here is an example
<materialMenu:SideMenu HorizontalAlignment="Left" x:Name="Menu"
MenuWidth="300"
Theme="Default"
State="Hidden">
<materialMenu:SideMenu.Menu>
<ScrollViewer VerticalScrollBarVisibility="Hidden">
<StackPanel Orientation="Vertical">
<Border Background="#337AB5">
<Grid Margin="10">
<TextBox Height="150" BorderThickness="0" Background="Transparent"
VerticalContentAlignment="Bottom" FontFamily="Calibri" FontSize="18"
Foreground="WhiteSmoke" FontWeight="Bold">Welcome</TextBox>
</Grid>
</Border>
<materialMenu:MenuButton Text="Administration"></materialMenu:MenuButton>
<materialMenu:MenuButton Text="Packing"></materialMenu:MenuButton>
<materialMenu:MenuButton Text="Logistics"></materialMenu:MenuButton>
</StackPanel>
</ScrollViewer>
</materialMenu:SideMenu.Menu>
</materialMenu:SideMenu>

wrapping a data grid with a toolbox

I got a wpf application.
I want all my data grids in application to have a set of buttons above them.
Tried to use decorator and adorner without success(the dataGrid stopped showing rows)
Any suggestions?
Given that you're wanting to have functionality behind the toolbox buttons (which I assume will require a reference to the grid) it probably makes sense to inherit from a HeaderedContentControl for this. This does mean that you can put any content in the control, but it would be possible to put override the metadata to add validation for this.
Anywhere, here's the xaml:
<!-- ToolBoxGridControl.xaml -->
<HeaderedContentControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfApplication3.ToolBoxGridControl">
<HeaderedContentControl.Header>
<StackPanel Orientation="Horizontal">
<Button/>
<Button/>
<Button/>
</StackPanel>
</HeaderedContentControl.Header>
<HeaderedContentControl.Template>
<ControlTemplate TargetType="HeaderedContentControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" Content="{TemplateBinding Header}"/>
<ContentControl Grid.Row="1" Content="{TemplateBinding Content}"/>
</Grid>
</ControlTemplate>
</HeaderedContentControl.Template>
</HeaderedContentControl>
And the simple code-behind (where you can put your toolbox implementation).
public partial class ToolBoxGridControl : HeaderedContentControl
{
private DataGrid DataGrid { get { return (DataGrid)Content; } }
public ToolBoxGridControl()
{
this.InitializeComponent();
}
}
To actually use, you can just add the following to your XAML with your data grid
<local:ToolBoxGridControl>
<DataGrid/>
</local:ToolBoxGridControl>

ItemsControl, VirtualizingStackPanel and ScrollViewer height

I want to display a important list of items using an ItemsControl.
The reason why I'm using an ItemsControl is that the DataTemplate is much more complex in the application I'm working on: The sample code provided only reflects the sizing problem I have.
I would like :
the ItemsControl to be virtualized because there is many items to display
its size to expand to its parent container automatically (the Grid)
<Grid>
<ItemsControl x:Name="My" ItemsSource="{Binding Path=Names}">
<ItemsControl.Template>
<ControlTemplate>
<StackPanel>
<StackPanel>
<TextBlock Text="this is a title" FontSize="15" />
<TextBlock Text="This is a description" />
</StackPanel>
<ScrollViewer CanContentScroll="True" Height="400px">
<VirtualizingStackPanel IsItemsHost="True" />
</ScrollViewer>
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
The code behind is :
public partial class Page1: Page
{
public List<string> Names { get; set; }
public Page1()
{
InitializeComponent();
Names = new List<string>();
for(int i = 0; i < 10000; i++)
Names.Add("Name : " + i);
My.DataContext = this;
}
}
As I force the ScrollViewer height to 400px, ItemsControl virtualization works as I expect: The ItemsControl displays the list very quickly, regardless of how many items it contains.
However, if I remove Height="400px", the list will expand its height to display the whole list, regardless its parent container height. Worse: it appears behind its container.
Putting a scrollviewer around the ItemsControl gives the expected visual result, but the virtualization goes away and the list takes too much time to display.
How can I achieve both automatic height expansion and virtualization of my ItemsControl ?
The problem is in the ItemsControl.Template: you use StackPanel there, which gives her children as much height as they want. Replace it to something like
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Text="this is a title" FontSize="15" />
<TextBlock Text="This is a description" />
</StackPanel>
<ScrollViewer CanContentScroll="True" Grid.Row="1">
<VirtualizingStackPanel />
</ScrollViewer>
</Grid>
And it should work fine.
Hope it helps.

Resources