How to animate vertically scrolling dynamic text array? - wpf

I have an "array" of text which dynamically grows as data comes into the application, and I would like to be able to scroll (programmatically, not via direct user input) that array vertically, as well as add to it. I tried to put the data into a DataGrid but that's not really what I want (unless I heavily modify the DG which I'm hoping to avoid). I don't need the array contents to be selectable, just viewable and I will scroll the "current" array item into view. What WPF element(s) should I be using to display and dynamically grow the list?
Edit:
So here is my current XAML snippet:
<Canvas Grid.Column="1" Background="#FFF8D2D2" ClipToBounds="True">
<ItemsControl Canvas.Top="20" Canvas.Left="20" Name="PipeQueueIC" Height="45" Width="272" BorderThickness="1" BorderBrush="#FF149060" />
</Canvas>
and here is my code behind:
DoubleAnimation scrollQueue = new DoubleAnimation();
scrollQueue.By = -16;
scrollQueue.Duration = TimeSpan.FromSeconds(0.5);
PipeQueueIC.BeginAnimation(Canvas.TopProperty, scrollQueue);
However the whole ItemsControl is moved up and it does not "scroll" through the "viewing window". What am I doing wrong here?

Use an ItemsControl:
<ItemsControl ItemsSource="{Binding SomeListOfString}"/>
ViewModel:
public class ViewModel
{
public ObservableCollection<string> SomeListOfString {get;set;}
public ViewModel()
{
//... Initialize and populate the Collection.
}
}

Related

WPF - dynamically display Image with Text above

in my .net4-0 C# application I have a grid of 8x5 buttons. Each of them should display an image and a single letter (the hotkey to press) over the image (top left corner). Because the images depend on my data, it must be dynamically. All Images have the same size (100x100 pixel). The Image should fill the button nicely.
How can i achieve this?
My thoughts so far is, every time I load my data and change the display images, I manually create a Stackpanel, with a Image and TextBlock on it. But this doesn't fit well together.
Use a ListBox and put your list of data into ListBox.ItemSource Then you can create your own DataTemplate do specify how you want to display your data in that ListBox
For Example you can specify that you want your ListBox to be displayed with a 8x5 Grid. This of course depends on if you know that you will always display your Grid with 8x5 cells.
For the specific that you want to have a button with a letter on top as you said I would go with this.
<!--Resource-->
<DataTemplate DataType={x:YourViewModel}>
<StackPanel>
<TextBlock Content="{binding SomeText}"/>
<Button>
<Button.Content>
<Image Source="{binding YourImageSource}" Width="100px" Height="100px"/>
</Button.Content>
</Button>
</StackPanel>
</DataTemplate>
This is assuming that you're using MVVM which is highly recommended when working with WPF.
If you don't know what MVVM then start reading about it cause it will help your development in WPF to become so much better.
If my example is confusing, please provide feedback and I will make it more clearer.
EDIT Simple-MVVM Example
Let's say we want to display a Game with a title and picture
First we create the Model
public class Game
{
private string _title {get; set;}
private string _imagepath {get; set;} //We are not getting the image but the imagepath
Public string Title {get{return _title;} set{_title = value;}
Public string Imagepath set{_imagepath = value;}
}
Then we need a ViewModel. Normally the ViewModel doesn't create new data since the data should come from the Model (From maybe a Database), but for the sake of this example we create it in the ViewModel
public class GameViewModel
{
Public ObservableCollection<Game> Games {get; set;} //<--- This is where the View is binding to
public GameViewModel
{
ObservableCollection<Game> Games = new ObservableCollection<Game>(); //ObservableCollection is used to notify if we have added or removed or updated an item in the list.
Games.Add(new Game() {Title = "Warcraft 3", Image=//Where you have your images );
Games.Add(new Game() {Title = "Overwatch", Image=//Where you have your images );
this.Games = Games;
}
}
And now when to have our View to bind to this ViewModel in our XAML-code
<!--Basic xaml-->
<!--Resource-->
<DataTemplate DataType={x:Type local:Game}>
<StackPanel>
<TextBlock Content="{Binding Title}"/>
<Button>
<Button.Content>
<Image Source="{Binding Imagepath}" Width="100px" Height="100px"/>
</Button.Content>
</Button>
</StackPanel>
</DataTemplate>
Then in our ListBox
<ListBox ItemSource="{Binding Games}"/>
To get this to work you need to set the Datacontext in the View. Often when you create a new WPF-project the the View is called MainWindow.xaml
Add the ViewModel to the datacontext like this
/*Normally you want to avoid doing anything in code-behind with MVVM
If you want to avoid that you have to look into DI and IoC But it is way
to much to do in this example*/
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new GameViewModel();
}
}

Programmatically Add Controls to WPF Form

I am trying to add controls to a UserControl dynamically (programatically). I get a generic List of objects from my Business Layer (retrieved from the database), and for each object, I want to add a Label, and a TextBox to the WPF UserControl and set the Position and widths to make look nice, and hopefully take advantage of the WPF Validation capabilities. This is something that would be easy in Windows Forms programming but I'm new to WPF. How do I do this (see comments for questions) Say this is my object:
public class Field {
public string Name { get; set; }
public int Length { get; set; }
public bool Required { get; set; }
}
Then in my WPF UserControl, I'm trying to create a Label and TextBox for each object:
public void createControls() {
List<Field> fields = businessObj.getFields();
Label label = null;
TextBox textbox = null;
foreach (Field field in fields) {
label = new Label();
// HOW TO set text, x and y (margin), width, validation based upon object?
// i have tried this without luck:
// Binding b = new Binding("Name");
// BindingOperations.SetBinding(label, Label.ContentProperty, b);
MyGrid.Children.Add(label);
textbox = new TextBox();
// ???
MyGrid.Children.Add(textbox);
}
// databind?
this.DataContext = fields;
}
Alright, second time's the charm. Based on your layout screenshot, I can infer right away that what you need is a WrapPanel, a layout panel that allows items to fill up until it reaches an edge, at which point the remaining items flow onto the next line. But you still want to use an ItemsControl so you can get all the benefits of data-binding and dynamic generation. So for this we're going to use the ItemsControl.ItemsPanel property, which allows us to specify the panel the items will be put into. Let's start with the code-behind again:
public partial class Window1 : Window
{
public ObservableCollection<Field> Fields { get; set; }
public Window1()
{
InitializeComponent();
Fields = new ObservableCollection<Field>();
Fields.Add(new Field() { Name = "Username", Length = 100, Required = true });
Fields.Add(new Field() { Name = "Password", Length = 80, Required = true });
Fields.Add(new Field() { Name = "City", Length = 100, Required = false });
Fields.Add(new Field() { Name = "State", Length = 40, Required = false });
Fields.Add(new Field() { Name = "Zipcode", Length = 60, Required = false });
FieldsListBox.ItemsSource = Fields;
}
}
public class Field
{
public string Name { get; set; }
public int Length { get; set; }
public bool Required { get; set; }
}
Not much has changed here, but I've edited the sample fields to better match your example. Now let's look at where the magic happens- the XAML for the Window:
<Window x:Class="DataBoundFields.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataBoundFields"
Title="Window1" Height="200" Width="300">
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisConverter"/>
</Window.Resources>
<Grid>
<ListBox x:Name="FieldsListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}" VerticalAlignment="Center"/>
<TextBox Width="{Binding Length}" Margin="5,0,0,0"/>
<Label Content="*" Visibility="{Binding Required, Converter={StaticResource BoolToVisConverter}}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"
Height="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=ActualHeight}"
Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=ActualWidth}"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
First, you will notice that the ItemTemplate has changed slightly. The label is still bound to the name property, but now the textbox width is bound to the length property (so you can have textboxes of varying length). Furthermore, I've added a "*" to any fields that are required, using a simplistic BoolToVisibilityConverter (which you can find the code for anywhere, and I will not post here).
The main thing to notice is the use of a WrapPanel in the ItemsPanel property of our ListBox. This tells the ListBox that any items it generates need to be pushed into a horizontal wrapped layout (this matches your screenshot). What makes this work even better is the height and width binding on the panel- what this says is, "make this panel the same size as my parent window." That means that when I resize the Window, the WrapPanel adjusts its size accordingly, resulting in a better layout for the items.
It is not recommended to add controls like this. What you ideally do in WPF is to put a ListBox(or ItemsControl) and bind your Business object collection as the itemsControl.ItemsSource property. Now define DataTemplate in XAML for your DataObject type and you are good to go, That is the magic of WPF.
People come from a winforms background tend to do the way you described and which is not the right way in WPF.
I would listen to Charlie and Jobi's answers, but for the sake of answering the question directly... (How to add controls and manually position them.)
Use a Canvas control, rather than a Grid. Canvases give the control an infinite amount of space, and allow you to position them manually. It uses attached properties to keep track of position. In code, it would look like so:
var tb = new TextBox();
myCanvas.Children.Add(tb);
tb.Width = 100;
Canvas.SetLeft(tb, 50);
Canvas.SetTop(tb, 20);
In XAML...
<Canvas>
<TextBox Width="100" Canvas.Left="50" Canvas.Top="20" />
</Canvas>
You can also position them relative to the Right and Bottom edges. Specifying both a Top and Bottom will have the control resize vertically with the Canvas. Similarly for Left and Right.

What techniques can I employ to create a series of UI Elements from a collection of objects using WPF?

I'm new to WPF and before I dive in solving a problem in completely the wrong way I was wondering if WPF is clever enough to handle something for me.
Imagine I have a collection containing objects. Each object is of the same known type and has two parameters. Name (a string) and Picked (a boolean).
The collection will be populated at run time.
I would like to build up a UI element at run time that will represent this collection as a series of checkboxes. I want the Picked parameter of any given object in the collection updated if the user changes the selected state of the checkbox.
To me, the answer is simple. I iterate accross the collection and create a new checkbox for each object, dynamically wiring up a ValueChanged event to capture when Picked should be changed.
It has occured to me, however, that I may be able to harness some unknown feature of WPF to do this better (or "properly"). For example, could data binding be employed here?
I would be very interested in anyone's thoughts.
Thanks,
E
FootNote: The structure of the collection can be changed completely to better fit any chosen solution but ultimately I will always start from, and end with, some list of string and boolean pairs.
I would strongly recommend the ItemsControl, its behaviour is as close as you can get to the ASP.Net repeater control so it is very flexible.
Declare the item control as:
<ItemsControl Name="YourItemsControl"
ItemsSource="{Binding Path=YourCollection}"
ItemTemplate="{StaticResource YourTemplate}">
</ItemsControl>
Then you can use the datatemplate to organise the data into a display format for the user
<DataTemplate x:Key="ProjectsTemplate">
<StackPanel Margin="0,0,0,10">
<Border CornerRadius="2,2,0,0" Background="{StaticResource ItemGradient}" d:LayoutOverrides="Width, Height">
<local:ItemContentsUserControl Height="30"/>
</Border>
...
Useful ItemsControl Links
http://drwpf.com/blog/itemscontrol-a-to-z/
http://www.galasoft.ch/mydotnet/articles/article-2007041201.aspx
I hope this helps you.
You can use Data Templates. Here's a good post about it.
This is exactly the kind of scenario WPF simplifies. Event-handlers- bah! Data-binding and data templates make this a cinch. I have constructed an example illustrating how you can do this.
Here is the code-behind, which declares a class to represent your items- PickedItem. I then create a collection of these items and populate it with some samples.
public partial class DataBoundCollection : Window
{
public DataBoundCollection()
{
Items = new ObservableCollection<PickedItem>();
Items.Add(new PickedItem("Item 1"));
Items.Add(new PickedItem("Item 2"));
Items.Add(new PickedItem("Item 3"));
InitializeComponent();
}
public ObservableCollection<PickedItem> Items
{
get;
set;
}
}
public class PickedItem
{
public PickedItem(string name)
{
Name = name;
Picked = false;
}
public string Name
{
get;
set;
}
public bool Picked
{
get;
set;
}
}
Now, let's look at the XAML mark-up for this window:
<Window x:Class="TestWpfApplication.DataBoundCollection"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBoundCollection" Height="300" Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Picked}" Margin="5"/>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I create a ListBox to hold the items, and bind its ItemsSource property to the collection I created in the code-behind. Then, I provide the ListBox with an ItemTemplate, which determines how each PickedItem will be rendered. The DataTemplate in this case is as simple as a check-box and some text, both bound to the member variables on PickedItem. Now, when I check any of these items, the data in the underlying collection is modified, in real-time, with no event handlers needed. Ta-da!
alt text http://img405.imageshack.us/img405/1083/databoundcollection.png

WPF Displaying Parent Child relationship

I can't wrap my head around how to accomplish rendering this
public class Shape{}
public class Circle: Shape{}
public class Square: Shape
{
public List<Circle> CircleList{ get; private set; }
}
I have a List that holds Shape objects, now what I want to accomplish is having each object rendered in a grid.
If the object is a Square there should be a nested grid that holds Circle items from the CircleList property
I've tried with an ItemsControl and a HierarchicalDataTemplate, could not get it working, i've tried nesting an ItemsControl inside an ItemsControl, i'm pretty new to WPF so i'm kinda fumbling around here not knowing what the "proper" solution would be. I did manage to render the above in a TreeView, but what i'm trying to accomplish is a drawingboard that renders shapes.
UPDATE
The "Drawingboard" should contain items, each item should be rendered in a container.
If the object is of Type Square the Square container should have a nested container to hold the Circle objects from the CircleList Property.
Scott is pretty close but not quite there; setting the DataContext of the Grid will not render the contained Circle objects. What you need is an embedded control that can render its own items, and then bind the ItemsSource property of that control to the CircleList.
I have constructed an example using your original classes that demonstrates this. Here is the code-behind:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Square square = new Square();
square.CircleList = new List<Circle>() { new Circle(25) };
_shapes.Add(square);
}
private List<Shape> _shapes = new List<Shape>();
public List<Shape> Shapes
{
get { return _shapes; }
}
}
public abstract class Shape { }
public class Circle : Shape
{
public double Diameter { get; private set; }
public Circle(double diameter)
{
Diameter = diameter;
}
}
public class Square : Shape
{
public List<Circle> CircleList { get; set; }
}
So you can see I have added a single Square to my Shapes list, that contains a circle of diameter 25. Note that this does not add any support for positioning the shapes using absolute coordinates; I assume you already have something in place for that.
Now the XAML:
<Window x:Class="TestWpfApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWpfApplication"
Title="Window1"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Circle}">
<Ellipse Stroke="Black"
Width="{Binding Diameter}"
Height="{Binding Diameter}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Square}">
<Border BorderThickness="1" BorderBrush="Black">
<ItemsControl ItemsSource="{Binding CircleList}"/>
</Border>
</DataTemplate>
</Window.Resources>
<ListBox ItemsSource="{Binding Shapes}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
Here are your DataTemplates; you can see the Circle is rendered simply with an Ellipse. The Square, on the other hand, has an embedded ItemsControl that renders its contained items. I have also drawn a Border around it to make the square shape.
Here is the result:
alt text http://img212.imageshack.us/img212/8658/squarewithcirclecontent.png
You could try using two DataTemplates, one for a Circle (just renders a Circle) and one for a Square. The Square DataTemplate should render a Grid (just give it a border to make it look like a square) and then set the nested Grid's DataContext="{Binding CircleList}".
I'm not 100% sure how you're converting a list of shapes to a grid, but sounds like you've already got that solved, so I'll just omit that for simplicity. :)

How to rotate Items in a WPF Listbox style?

I want to make a WPF ListBox photo album for one my college projects.
I need to design a DataTemplate/ListBox style so it will look like a stack jumbled of photos, i.e., the top one being the item in focus/selected (see diagram below).
Image here
With reference to the drawing,
item 1) is not shown
item 2) is at the back of stack
item 3) in the middle of 2 and 4
item 4) is in focus
item 5) is not shown
I am having the most trouble getting the items to rotate and overlap and the most difficult task is getting the item in focus to be shown on top.
I'm using Visual Basic because I haven't yet mastered C# so it would be useful if examples could be in VB or use mainly WPF.
To get the items to rotate, you should look at using Transforms. The one that is most relevant in this case is the Rotate Transform, also to give it that random scattered apperance, we can use an ObjectDataProvider and generate our angles somewhere else.
I don't know VB, but the only C# involved in this is pretty simple, and should be easily transferable.
Lets just use something simple like Colors, which can easily be switched over to image resource paths. Here we've got an ObservableCollection of our Colors, and also a separate class that we will use to generate angles, all it's going to do is return a number between 0 and 360, which we will use to rotate each item.
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Colors = new ObservableCollection<string>();
Colors.Add("Red");
Colors.Add("Blue");
Colors.Add("Green");
Colors.Add("Yellow");
this.DataContext = this;
}
public ObservableCollection<string> Colors
{
get;
set;
}
}
public class AngleService
{
private static Random rand = new Random();
public int GetAngle()
{
return rand.Next(0, 90);
}
}
In the XAML, we can now create a resource that can be used to generate the angles.
<Window.Resources>
<local:AngleService x:Key="Local_AngleService" />
<ObjectDataProvider x:Key="Local_AngleProvider"
x:Shared="False"
ObjectInstance="{StaticResource Local_AngleService}"
MethodName="GetAngle" />
</Window.Resources>
Now, we just need to create something to display our items. We can put them in a ListBox and add a data template to set the background for each color item, as well as apply the RotateTransform.
<ListBox ItemsSource="{Binding Colors}"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center">
<ListBox.ItemTemplate>
<DataTemplate>
<Border x:Name="uiItemBorder"
BorderBrush="Black"
BorderThickness="2"
CornerRadius="3"
Background="{Binding}"
Width="50"
Height="50">
<TextBlock Text="{Binding}"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
<Border.RenderTransform>
<RotateTransform Angle="{Binding Source={StaticResource Local_AngleProvider}}" />
</Border.RenderTransform>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The UI still needs a bit of work from there, but that should help out with the rotation of the items.

Resources