Zooming an item in Listbox/SurfaceListbox WPF - wpf

I am working on SurfaceListbox but I think the logic would apply to normal WPF listbox also.
I have the surfacelistbox with horizontal scroll enabled. It contains close to 20 items. The surfacelistbox is going to be placed in the center of the screen. Now when the user scrolls the listbox, the items move horizontally and based on the size of each item in the listbox, I have seen generally 3 items are visible at any given time on the screen.
Now what I want to do is, when the user stops scrolling and the 3 items are visible, I want to zoom in the center item i.e. basically enlarge it to highlight it.
Any pointers on how to implement such functionality in WPF would be great. Since the user doesnt make a selection when scrolling I could not use the selectionchanged event to know which one is the center item.

Place the SurfaceListBox in a Grid and bind ScaleTransform to a slider with a range from 1 to 5 centered in the Grid using RenderTransformOrigin="0.5,0.5".
The ItemsSource is set to an ObservableCollection; I include some definitions below for completeness to help others follow. I can provide more code if needed.
Here is the View:
<Window x:Class="SurfaceControls.MainWindow"
Icon="/Images/logo.gif"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Surface Toolkit Controls" Height="500" Width="600"
xmlns:my="http://schemas.microsoft.com/surface/2008" >
<Window.Resources>
<DataTemplate x:Key="EquipmentItemStyle">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=Id}"/>
<TextBlock Text="{Binding Path=EquipmentName}"/>
<TextBlock Text="{Binding Path=EquipmentType}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid RenderTransformOrigin="0.5,0.5" Grid.Row="0">
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform
ScaleY="{Binding Path=Value, ElementName=slider}"
ScaleX="{Binding Path=Value, ElementName=slider}"/>
</TransformGroup>
</Grid.RenderTransform>
<my:SurfaceListBox
ItemsSource="{Binding Path=Equipment, Mode=TwoWay}"
SelectedItem="{Binding Path=SelectedEquipment, Mode=TwoWay}"
ItemTemplate="{StaticResource ResourceKey=EquipmentItemStyle}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Stretch"
HorizontalAlignment="Center"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</my:SurfaceListBox>
</Grid>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<my:SurfaceSlider Ticks="5"
Width="100"
Minimum="1"
Interval="1"
Maximum="5"
x:Name="slider"
VerticalAlignment="Center"
Margin="4,4,4,4"/>
</StackPanel>
</Grid>
</Window>
Here this collection bound by the ListBox:
private ObservableCollection<Equipment> _equipment = new ObservableCollection<Equipment>();
public ObservableCollection<Equipment> Equipment
{
get
{
return _equipment;
}
set
{
_equipment = value;
}
}
And the defintion of the model:
public class Equipment
{
public int Id { get; set; }
public string EquipmentName { get; set; }
public string EquipmentType { get; set; }
}

Got useful information from these two links
http://social.msdn.microsoft.com/Forums/en-US/surfaceappdevelopment/thread/290f18c3-9579-4578-b215-45e6eb702470
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/5d486826-9a72-4769-bd09-ff6977e16c30

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);
}
}

Binding UserControl Width To A TextBox Input

It's my first time using the MVVM pattern and I have a bit of trouble understanding how everything ties together.
I have a UserControl with a Textbox element which should change the Width of said UserControl based on it's input.
I'm facing two problems:
For my idea to work, I need to change and bind to d:DesignWidth and my ColumnDefinition Width. How and where do I implement those changes? Based on my knowledge of MVVM the View (in this case my UserControl) is controlled by a ViewModel for said UserControl. Is it nessesary to implement one or is it possible to bind directly to both properties? I know I can name my ColumnDefinition with x:Name="MyColumnDefinition" but is the same possible for the actual UserControl Width?
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="170">
I have an ObservableCollection filled with two different UserControls and I want the Controls not to overlap when I display them. I use a ListBox element to display the ObservableCollection and implement the different UserControls over DataTemplates with a DataTemplateSelector. This works fine now but I'm worried if I dynamically change the Control Width that it will just overlap the next Control in the list. How do I ensure this won't happen?
Below is the code I have for now for the UserControl:
<Border Background="LightGray" CornerRadius="6">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Button VerticalAlignment="Top" HorizontalAlignment="Right" Grid.Column="2" Grid.Row="0"
BorderThickness="0" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=DeleteCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=DeleteCommandParameter}">
<Rectangle Width="8" Height="8" Fill="White">
<Rectangle.OpacityMask>
<VisualBrush Visual="{StaticResource appbar_close}" Stretch="Fill" />
</Rectangle.OpacityMask>
</Rectangle>
</Button>
<TextBlock Grid.Column="1" Grid.Row="0" FontSize="12" Margin="0,4,0,18" Foreground="White" HorizontalAlignment="Center" Grid.RowSpan="2">Delay</TextBlock>
<TextBox Grid.Column="1" Grid.Row="1" Width="46" Margin="0,4,0,16" HorizontalAlignment="Center" Grid.RowSpan="2"
Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=Delay.MiddlePosition, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Grid.Column="1" Grid.Row="2" FontSize="8" Margin="20,5,20,5" Foreground="Gray" HorizontalAlignment="Center">[s]</TextBlock>
</Grid>
</Border>
Edit:
ListBox-XAML to hold the other UserControls (I'm trying to build an Axis which can be filled with custom Positioning- and DelayControls:
<ListBox Name="Test" SelectionMode="Single" Grid.Column="1"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=BlockList}"
ItemTemplateSelector="{StaticResource BlockTemplateSelector}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
End result should look kind of like this, but with differently sized Positioning and Delay blocks:
Check this code will help you to set width of one control to other control.
<Border>
<Grid x:Name="grv">
<TextBox Width="{Binding ElementName=grv,
Path=ActualWidth}">
</TextBox>
</Grid>
</Border>
I struggeled quite a while to figure out how to address your issue and even though I am not completely happy with the outcome, I managed to solve it.
First I create a ListBox with a DummyList, which contains Model-Objects called 'UserControlModel' with a singe Property 'modelWidth', from which I create my UserControls with their default size.
<ListBox ItemsSource="{Binding SimpleList, UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" Width="Auto" Height="200">
<ListBox.ItemTemplate>
<DataTemplate>
<osv:UserControl1 Width="{Binding modelWidth}" OnTextValidated="UserControlSizeChangeEvent"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
OnTextValidated is a RoutedEvent to hand up the KeyDown-Event from my Textbox to my Window(which i will show later)
The UserControl1.xaml then adds the textbox
<TextBox Width="60" Height="30" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" KeyDown="TextBox_KeyDown" Text="{Binding myText, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"></TextBox>
with a KeyDown event and a textbinding.
private void TextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return)//press enter to change
{
if (double.TryParse(myText, out double d) == true)
{
if (d >= 50) //minimum width, so i won't set it to 0 by accident
{
myWidth = d; //set the new Width
OnTextValidated(this, new RoutedEventArgs()); //tell window to resize the UserControl
}
}
}
}
Once I validated the new size is neither wrong nor too small i call a RoutedEventHandler
private RoutedEventHandler m_OnTextValidated;
/// <summary>
///
/// </summary>
public RoutedEventHandler OnTextValidated
{
get { return m_OnTextValidated; }
set
{
m_OnTextValidated = value;
OnPropertyChanged("CustomClick");
}
}
now i can bind on this like shown above.
next i have to do is passing down my event from the xaml.cs to the MinWindowViewModel
//Variables
private MainWindowViewModel m_DataContext;
//Constructor
DataContext = new MainWindowViewModel ();
m_DataContext = (MainWindowViewModel)this.DataContext;
private void UserControlSizeChangeEvent(object sender, RoutedEventArgs e)
{
if (m_DataContext != null)
{
m_DataContext.UserControlSizeChangeEvent(sender, e);
}
}
and finally update the size of my object in my code behind
public void UserControlSizeChangeEvent(object sender, RoutedEventArgs e)
{
UserControl1 uc = sender as UserControl1;
uc.Width = uc.myWidth;
}
Note: Although this works quite fine i'd be much happier if i found a way to change the width of the model instead of the object, so they would still have the same width in case the list is redrawn.
I also didn't use a MVVM pattern for my UserContrl, so you'll have to pass the event from your xaml.cs to your viewmodel first like I did for the MainWindow

How to make WPF listview display Images and Labels dynamically

I'm having quite a difficult time trying to create the UI for a WPF Window. I'm trying to display (dynamically) a bunch of Movie Posters with the name of the movie directly under the image. ItemsSource is assigned to a list of Images via foreach iteration. The Image files themselves may be different sizes, but as shown below I will be setting a uniform size.
Basically, my goal is for it to look something like this:
So far, My code only displays a window with one large horizontal row(?) with the image in the center and no label. Here's my XAML code:
<Window x:Name="TVWindow" x:Class="PACS_Pre_Alpha.TV"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TV" Height="746" Width="1000" ResizeMode="NoResize">
<Grid x:Name="TVGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<ListView x:Name="TvBox" HorizontalAlignment="Left" Height="648" VerticalAlignment="Top" Width="994" Grid.Row="5" Grid.Column="5">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" VerticalAlignment="Stretch">
<Image Source="{Binding ImageData}" HorizontalAlignment="Center" VerticalAlignment="Top" />
<TextBlock Text="{Binding Title}" HorizontalAlignment="Center" VerticalAlignment="Bottom" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
My movies are added with this C# code:
foreach (string tvf in ContentFiles)
{
string ContentTitle = System.IO.Path.GetFileNameWithoutExtension(tvf);
MovieData cnt = new MovieData();
cnt.ImageData = LoadImage(ActualImage);
cnt.Title = ContentTitle;
ContentDataList.Add(cnt);
}
TvBox.ItemsSource = ContentDataList;
Edit: I have changed my XAML Markup as #MarkFeldman suggested, but now nothing appears.
Edit: It currently looks like this:
You're going to provide more info about the data itself i.e. what's it's format, how are you assigning it to the ItemsSource etc. For one thing you're not setting the ItemTemplate, so you might want to look at that first. For example if you have a class containing your movie data that looks like this:
public class MovieData
{
private string _Title;
public string Title
{
get { return this._Title; }
set { this._Title = value; }
}
private BitmapImage _ImageData;
public BitmapImage ImageData
{
get { return this._ImageData; }
set { this._ImageData = value; }
}
}
Then you would display it with something like this:
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" VerticalAlignment="Stretch">
<Image Source="{Binding ImageData}" HorizontalAlignment="Center" VerticalAlignment="Top"/>
<TextBlock Text="{Binding Title}" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
UPDATE:
Sorry, I thought it was obvious that you still needed to use a UniformGrid. Here is what your full XAML should look like:
<ListView x:Name="TvBox" HorizontalAlignment="Stretch" VerticalAlignment="Top">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="5" HorizontalAlignment="Stretch"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Image Source="{Binding ImageData}" HorizontalAlignment="Stretch" VerticalAlignment="Top" Stretch="UniformToFill" />
<TextBlock Text="{Binding Title}" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I've already provided you with the MovieData class, so here's what your Window code should look like:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.TvBox.ItemsSource = new MovieData[]
{
new MovieData{Title="Movie 1", ImageData=LoadImage("image.jpg")},
new MovieData{Title="Movie 2", ImageData=LoadImage("image.jpg")},
new MovieData{Title="Movie 3", ImageData=LoadImage("image.jpg")},
new MovieData{Title="Movie 4", ImageData=LoadImage("image.jpg")},
new MovieData{Title="Movie 5", ImageData=LoadImage("image.jpg")},
new MovieData{Title="Movie 6", ImageData=LoadImage("image.jpg")}
};
}
// for this code image needs to be a project resource
private BitmapImage LoadImage(string filename)
{
return new BitmapImage(new Uri("pack://application:,,,/" + filename));
}
}
In this example I'm assuming there is an image in your project called "image.jpg" which has been set to build action "Resource", if your images come from elsewhere then you'll need to modify the LoadImage code accordingly.
I have done something very similar with UniformGrid I see you did not set the Rows of your UniformGrid. I did this In my Game App. Good approach but difficult to get right. Set an ItemTemplate. And try an ItemsControl Outer Object instead of listview
<ItemsControl IsEnabled="{Binding GameBoardEnabled}"
x:Name="_board"
ItemsSource ="{Binding Board}"
ItemTemplate= "{DynamicResource GamePieceTemplate}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!--<StackPanel/>-->
<UniformGrid
Columns="{Binding Columns}"
Rows ="{Binding Rows}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

TextBlock1 is not declared. It may be inaccessible due to its protection level

As silly as this sounds I'm a little stumped at this one. Here's my XAML in a Win Phone 8 App:
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock Text="Page" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<phone:LongListSelector x:Name="MainLongListSelector" Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="MainLongListSelector_SelectionChanged">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock x:Name="TextBlock1" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
</Grid>
I've searched around but I don't know why I can't write code against the TextBlock1 control in code behind. When I type TextBlock1.Text= .... I get the error TextBlock1 is not declared. It may be inaccessible due to its protection level. But I can't see how it is private?
All I'm trying to do is add a textblock, assign some content to it, and then that selected value is passed across another page to perform relevant action.
In addition as soon as I remove it outside of the PhoneListSelector I can access it.
TextBlock1 is defined inside an ItemTemplate, anything defined a Template cannot be access directly as it will be created on runtime by the control.
You probably need to do binding on the TextBlock if you want to manipulate anything that the LongListSelector's DataContext has.
<phone:LongListSelector x:Name="MainLongListSelector" Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="MainLongListSelector_SelectionChanged">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock x:Name="TextBlock1" Text="{Binding Content"} HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
MainLongListSelector.DataContext = new List<TestViewModel>();
public class TestViewModel : INotifyPropertyChanged
{
//Assuming you've implemented the interface
private string _content;
public string Content { get { return _content; } { set { _content = value; NotifyOfPropertyChanged("Content"); } }
}
From here, you can try to access the selected value content and pass that to the next page.
var selectedItem = MainLongListSelector.SelectedItem as TestViewModel;
GoToNextPage(selectedItem.Content);
I strongly suggest to read MVVM design pattern and everything should be easy for you to implement, always remember UI is not DATA it's responsibility is only to show something that is passed through the ViewModel.

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