ItemsControl and Canvas - only the first item is visible - c#

I have a canvas which contains several different shapes, which are all static, and bound to different properties in the view model (MVVM). As of now, the canvas is defined as the following (simplified):
<Canvas>
<Polygon Fill="Red" Stroke="Gray" StrokeThickness="3" Points="{Binding StorageVertices}" />
<Ellipse Fill="Blue" Width="{Binding NodeWidth}" Height="{Binding NodeHeight}" />
<!-- And some more static shapes -->
<!-- ... -->
</Canvas>
To this canvas, I want to add a dynamic list where each entry is converted to a polygon. I thought that the best approach would be an ItemsControl. This is what I've used in my approach but only the first item in the collection (list) is displayed.
<Canvas>
<!-- ... -->
<!-- Same canvas as earlier with the addition of the ItemsControl -->
<ItemsControl ItemsSource="{Binding Offices, Mode=OneWay, Converter={...}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Polygon Fill="AliceBlue" Stroke="Gray" StrokeThickness="1" Points="{Binding Points}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
With this code, only the first item in the Offices collection is displayed. How come? If I view the visual tree all polygons are within it. I'm very new to WPF, so I can only guess, but my first thought was that the default of a StackPanel as an ItemPresenter might be inappropriate in this case, but I can only guess...

Well, a few things to note here. Firstly, when working with the Canvas panel, each item within the panel will be placed at the top-left unless a relative location is specified. Here is an example of a Canvas with your elements, one placed near the top (40 pixels down, 40 to the right), the other placed at the bottom (100 pixels to the left from the right edge):
<Canvas>
<Polygon Canvas.Left="40" Canvas.Top="40" ... />
<Ellipse Canvas.Right="100" Canvas.Bottom="0" ... />
</Canvas>
Now, remember that a Canvas is a type of Panel. It's main purpose is not to be some sort of list, but to moreover define how a control (or controls) are presented. If you wish to actually present a collection/list (enumeration) of controls, then you should use a type of ItemsControl. From there, you can specify the ItemsSource and customize the ItemsPanel (as well as the ItemTemplate, which might be necessary).
Secondly, and this comes up often, is "How do I add static elements to an ItemsSource that is databound?", to which the answer is to use the CompositeCollection, and the subsequent CollectionContainer. In your situation you have two (2) static items (plus more) that you wish to add to your Offices collection. I'm guessing that these "static shapes" are really a substitute to an image of a floorplan.
Here is a sample of what your XAML would look like if you wish to draw your floorplan:
<ItemsControl>
<ItemsControl.Resources>
<CollectionViewSource x:Key="cvs" Source="{Binding Floors}" />
</ItemsControl.Resources>
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource cvs}" />
<!-- Static Items -->
</CompositeCollection>
</ItemsControl.ItemsSource>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas ... />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I'm not sure what each of your objects in your Floor collection is, but they should not be any type of shape at all. They should be a some object that simply states information about location of the office, color, etc. Here is an example I'm guessing at since you didn't provide what the collection of items was composed of:
// This can (and should) implement INotifyPropertyChanged
public class OfficeViewModel
{
public string EmployeeName { get; private set; }
public ReadOnlyObservableCollection<Point> Points { get; private set; }
...
}
public class Point
{
public double X { get; set; }
public double Y { get; set; }
}
From here you would use a DataTemplate to translate the object (model/viewmodel) into what it should look like on your view:
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Polygon Points="{Binding Points}" Color="AliceBlue" ... />
<DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Of course, if you wish to have multiple representations of what each item looks like from your collection, Offices, then you'll have to take advantage of the DataTemplateSelector (which will be set to the ItemsControl.ItemTemplateSelector property) to select from a set of DataTemplates. Here's a good answer/reference to that: https://stackoverflow.com/a/17558178/347172
And finally, one last note... keep everything to scale and your points as types of double. Personally I would always use the scale 0-1, or 0-100. As long as all your points and static items fit within that bounds, you can stretch out your ItemsControl to any height/width and everything inside will also adjust and match up just fine.
Update: It's been quite some time and I forgot that the CompositeCollection class is not a type of FrameworkElement, so it doesn't have a DataContext. If you want to databind one of of your collections, you must specify a reference to a FrameworkElement with the desired DataContext:
<CollectionContainer Collection="{Binding DataContext.Offices, Source={x:Reference someControl}}"/>
Update 2: After digging online for awhile, I found a better way to allow databinding to work with the CompositeCollection, the answer section above has been updated to account for this by using CollectionViewSource to create a resource bound to the collection. This is much better than using the x:Reference. Hope that helps.

Try to set
yourItemsControl.DataContext = Offices;
in code behind.

Related

WPF - Setting tab order on large number of controls

So I have a large amount of controls (textboxes) as you can see below, but there are around 30 rows of this. These are loaded using arrays, and each column represents an array. So when I hit tab in a textbox, instead of tabbing horizontally, it tabs vertically instead.
Is there a way to set the tab order so it will tab horizontally, aside from changing the way the controls are loaded?
Another quirk is that when leaving one textbox, instead of focusing the next, it just kind of highlights the textbox, and I have to tab a second time to get inside the next textbox.
EDIT:
Main view (lots of code has been omitted, I'm pretty sure nothing has been left out that needs to be here)
<ListBox ItemsSource="{Binding Items}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
ItemsView
<UserControl>
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:Item}">
<views:ItemView/>
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<ContentControl Content="{Binding item <!-- about 30 different items here, omitted for readability -->}" />
</StackPanel>
</UserControl>
ItemView
<UserControl ... IsTabStop="False">
<TextBox Text="{Binding Value}" />
</UserControl>
The ItemView is nested in the ItemsView, which is nested in the MainView. Since the textboxes are generated based on the array values, I can't easily set the TabIndex property unless there is a way I don't know about (I am pretty new at WPF).
The TabIndex property provides a way to control the tab order independently of the order controls are loaded.
Usage example:
<Grid>
<TextBox TabIndex="2" /><!-- will receive focus second -->
<TextBox TabIndex="1" /><!-- will receive focus first-->
</Grid>
I would guess the unwanted focusing you are seeing is due to a parent UserControl that your TextBoxes are placed in.
If this is the case, you could prevent that by setting IsTabStop="false" on that parent control.
For example:
<UserControl .... IsTabStop="False">
<Grid>
<!-- other graphics -->
<TextBox TabIndex="1" />
</Grid>
</UserControl>
Using a view model to populate the data
public class CellViewModel
{
public double Value { get; set; }
public int TabIndex { get; set; }
}
public IEnumerable<IEnumerable<CellViewModel>> GetMatrix(
List<List<double>> matrixValues)
{
var columnCount = matrixValues.Count;
return matrixValues
.Select((x, i) => GetColumn(x, columnCount, i));
}
public IEnumerable<CellViewModel> GetColumn(
List<double> columnValues,
int columnCount,
int columnIndex)
{
return columnValues
.Select((x, i) =>
new CellViewModel { Value = x, TabIndex = columnIndex + columnCount * i });
}
Your ItemsSource for your ListBox (which you've now changed to ItemsControl) should be a new Matrix property, which you populate using GetMatrix().
In your ItemView, you would want something like this:
<UserControl ... IsTabStop="False">
<TextBox Text="{Binding Value}" TabIndex="{Binding TabIndex}" />
</UserControl>

Bind StackPanel to method that adds child elements

Here's my issue: I have a wrapper class that contains sets of lists that contain 15 images each. I want to bind a central StackPanel to a method that actually modifies the same StackPanel that was passed to it and adds child StackPanel elements that contain 15 images each.
To clarify:
I have a central StackPanel that has a vertical orientation. This StackPanel is located inside of a DataTemplate!.
<DataTemplate>
<Grid x:Name="ImageDisplayGrid" Height="861" Width="656">
<StackPanel x:Name="CentralImagePanel" HorizontalAlignment="Left" Height="841" Margin="10,10,0,0" VerticalAlignment="Top" Width="636"/>
</Grid>
</DataTemplate>
I have many instances of my wrapper class that contain up to 15 images each (as WritableBitmap objects.
I want to bind my central StackPanel to some method that will modify that StackPanel, iterating through my list of wrapper classes and adding child StackPanel controls to the central StackPanel for each instance of my wrapper class found.
For each instance of the (ImageSet1, ImageSet2, etc for example) wrapper class, the new StackPanel that will be added to the central StackPanel will be populated using the images contained in that wrapper class instance.
In my mind there isn't really anything to be 'returned' here, so I was hoping there was a way to just pass the control (the central StackPanel) to some method, let the method modify it, and then carry on after the central StackPanel is populated with its child `StackPanels'.
To clarify even more:
Think of NetFlix. You know how you can scroll vertically through each category and each category allows you to scroll horizontally? Thats what I am trying to emulate, only I want it to be dynamic and bound to my wrapper class that contains a list of Images to use.
My main obstacle right now is that the central StackPanel is located within a DataTemplate, so there isn't an easy way to access it during runtime. On top of that it would be nicer to use a binding anyway.
I have tried to use IValueConverter to turn my wrapper class into a list of StackPanel objects that the central StackPanel can use, but that didn't work. I've also searched for ways to bind a control to a method that has no return property without any luck as well.
Any help or examples would be greatly appreciated.
You are thinking about this wrong. Really, really, wrong. StackPanel is a layout control. You shouldn't ever be directly modifying its children or any other properties.
As you've noticed, there is no real way to do this task in the way you describe.
To display collections, use an ItemsControl. In your case, it would be something like:
<ItemsControl ItemsSource="{Binding Categories}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource={"Binding Videos"}>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- Whatever -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Notice the inner template is another ItemsControl, this time with a horizontal StackPanel as the panel template.

hide or remove border of wpf button in C# code

I have a stackpanel named "mystack" in my xaml file and I am adding buttons in it dynamically from the .cs file and want to remove the border of buttons in C# .cs file
what I really want is to populate this stackpanel with the buttons coming from a list of string
thanks in advance
xaml:
<Grid HorizontalAlignment="Left" Height="227" Margin="10,10,0,0" Grid.Row="2"
VerticalAlignment="Top" Width="530">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Name="mystack" HorizontalAlignment="Left" Grid.Row="2"
VerticalAlignment="Top" Width="520"/>
</ScrollViewer>
</Grid>
.cs:
public List<String> Schools()
{
List<String> l = new List<string>();
l.Add("SST");
l.Add("SBE");
l.Add("SSH");
return l;
}
I agree with HighCore, you generally do not want to manipulate the UI elements in your code.
To remove the Border of the buttons you can set a Button's BorderThickness property to "0" in Xaml or to new Thickness(0) in the code-behind.
i.e.
myButton.BorderThickness = new Thickness(0);
EDIT:
Okay, I noticed your updated question. I would create a property that stores your list of schools and bind to it in a way similar to this:
public List<string> Schools
{
get { return _schools; }
set { _schools = value; }
}
Somewhere you need to set the DataContext of the control to your class containing the Schools property. If you are dynamically updating the list of Schools you'll need to implement INotifyPropertyChanged so the UI knows when to update. And then your Xaml would look something like this:
<ItemsControl ItemsSource="{Binding Schools}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" BorderThickness="0" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl>
You can't remove button's border like: btn.BorderThicknes=new Thickness(0);
See this: Removing button's border
The fast Fix:
What I had to do to effectively hide the button border - and due to the button control template I believe which utilizes and changes Button border (i.e. even if you remove it it'd draw it on some trigger I believe)...
...was to set BorderBrush="Transparent" as well (I always do BorderThickness as well but I'm guessing it's not needed - only for visual/layout look'n'feel)
i.e. setting thickness alone is not enough.
I'm really not sure that's the bets way to do it, or actually I'm
quite sure there must be something smarter - but I always end up with
that.
The Right Way:
Proper way - and recommended - is to write your own Button template -
based on the Microsoft official one - or base it on it - and do what
you need w/o borders.
For the code behind/C#:
You really don't need that as per your changed question - do what others suggested already
the best way to do this is :
<Style TargetType="Button">
<Style.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="0"/>
</Style>
</Style.Resources>
</Style>
what I really want is to populate this stackpanel with the buttons
coming from a list of string
That's called a ListBox:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" BorderThickness="0"/>
<!-- Whatever other customizations to the button -->
</DataTemplate
</ListBox.ItemTemplate>
</ListBox>
ViewModel:
public class ViewModel
{
public ObservableCollection<string> Items {get;set;}
public ViewModel()
{
Items = new ObservablecCollection<string>();
Items.Add("String1");
Items.Add("String2");
Items.Add("String3");
}
}
You need to learn the MVVM pattern.

Storing properties of objects in C#/WPF

I'm allowing user to drag/drop some objects from a toolbox and of course each object has a unique id. As soon as object is used, let's say placed on a grid or canvas, I need to show its properties so I need an array of objects where each object can hold its own properties.
Can you give me some advice and direction on how to implement a class to handle multiple objects while each object can hold on to let's say 10 properties?
The best solution is to use a PropertyGrid control; your application looks similar to Visual Studio and your implementation will be similar to that.
Have a look at this SO question for available PropertyGrid options you have -
Is there a Property Dialog control that i can use in my WPF App?
Now you can define a class for each control and declare normal CLR properties for that control; properties you don't want to display in PropertyGrid can be marked with BrowsableAttribute and PropertyGrid will honor that.
In case you want more control over what properties are displayed, you can create your own custom attribute and modify PropertyGrid implementation to use that attribute and display properties marked with this attribute.
Can you give me some advice and direction on how to implement a class
to handle multiple objects while each object can hold on to let's say
10 properties?
There is no need for you to implement such a class. The way I would handle this problem would be to have a common base class for all the objects in the toolbox (ToolboxItem for example) which only exposes properties and functionality common to all items in the toolbox.
public abstract class ToolboxItem
{
public string Name { get; set; }
public Point Position { get; set; }
}
You can then derive your specific items from this class E.G. TextToolboxItem and RectangleToolboxItem (or whatever you want). The derived classes can then expose only the properties they require.
public class TextToolboxItem : ToolboxItem
{
public string Text { get; set; }
}
public class RectangleToolboxItem : ToolboxItem
{
public Rect Bounds { get; set; }
}
To store these you could just use a generic collection such as:
ObservableCollection<ToolboxItem> items = new ObservableCollection<ToolboxItems>();
As long as the items derive from ToolboxItem they can all be held within the single collection and the individual properties can all be bound to using WPF's data binding features.
You can then create and expose the data in the following way:
public partial class MainWindow : Window
{
private ObservableCollection<ToolboxItem> items;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
items = new ObservableCollection<ToolboxItem>
{
new TextToolboxItem { Name = "primaryText",
Text = "Hello world",
Position = new Point(40, 130) },
new TextToolboxItem { Name = "secondaryText",
Text = "Hello world (again)",
Position = new Point(200, 30) },
new RectangleToolboxItem { Position = new Point(50,300),
Name = "Rect1",
Bounds = new Rect(0, 0, 150, 85) },
};
}
public ObservableCollection<ToolboxItem> Items { get { return items; } }
}
To display this information in the user interface I would do the following:
Use a grid to split the view into two sections. The first is where the properties of the selected item will be displayed and the second displays the 'design surface'
Use a ContentPresenter to display the properties of the selected item.
Use a ListBox with a custom ItemsPanel and ItemContainerStyle to 'draw' your items onto the design surface.
Use a DataTemplate to tell WPF how to render each item in both the 'property grid' and the 'design surface' (This post describes how to use a different DataTemplate for different objects).
The xaml required to achieve this is shown below:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="7*" />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{Binding ElementName=listBox, Path=SelectedItem}"
Margin="5">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type this:TextToolboxItem}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Position}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type this:RectangleToolboxItem}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Position}"/>
<TextBlock Text="{Binding Bounds}"/>
</StackPanel>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
<ListBox x:Name="listBox" Grid.Column="1"
Margin="5" ItemsSource="{Binding Items}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type this:TextToolboxItem}">
<TextBox Text="{Binding Text}"
Margin="10"/>
</DataTemplate>
<DataTemplate DataType="{x:Type this:RectangleToolboxItem}">
<Rectangle Width="{Binding Bounds.Width}"
Height="{Binding Bounds.Height}"
Stroke="DarkRed" Fill="Pink"/>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Position.X}"/>
<Setter Property="Canvas.Top" Value="{Binding Position.Y}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
The end result looks like this:
Notice that the properties of the selected item are shown in the left hand section of the window.
Now this solution is currently very crude but does demonstrate a starting point for you to develop this further. Ideas for improvement include:
Re-factoring the code into a viewModel so that it is MVVM compliant.
Handling drag and drop of the items on the 'design surface'.
Changing the `ContentPresenter' for a property grid to give you much richer support for displaying and editing the properties of the selected object.

How can I bind the nested viewmodels to properties of a control

I used Microsoft's Chart Control of the WPF toolkit to write my own chart control.
I blogged about it here. My Chart control stacks the yaxes in the chart on top of each other. As you can read in the article this all works quite well. Now I want to create a viewmodel that controls the data and axes in the chart. So far I'm able to add axes to the chart and show them in the chart. But I have a problem when I try to add the lineseries because it has one DependentAxis and one InDependentAxis property. I don't know how to assign the proper xAxis and yAxis controls to it.
Below you see part of the LineSeriesViewModel. It has a nested XAxisViewModel and YAxisViewModel property.
public class LineSeriesViewModel : ViewModelBase, IChartComponent
{
XAxisViewModel _xAxis;
public XAxisViewModel XAxis
{
get { return _xAxis; }
set
{
_xAxis = value;
RaisePropertyChanged(() => XAxis);
}
}
//The YAxis Property look the same
}
The viewmodels all have their own datatemplate.
The xaml code looks like this:
<UserControl.Resources>
<DataTemplate x:Key="xAxisTemplate" DataType="{x:Type l:YAxisViewModel}">
<chart:LinearAxis x:Name="yAxis" Orientation="Y" Location="Left" Minimum="0" Maximum="10" IsHitTestVisible="False" Width="50" />
</DataTemplate>
<DataTemplate x:Key="yAxisTemplate" DataType="{x:Type l:XAxisViewModel}">
<chart:LinearAxis x:Name="xAxis" Orientation="X" Location="Bottom" Minimum="0" Maximum="100" IsHitTestVisible="False" Height="50" />
</DataTemplate>
<DataTemplate DataType="{x:Type l:LineSeriesViewModel}">
<!--Binding doesn't work on the Dependent and IndependentAxis! -->
<!--YAxis XAxis and Series are properties of the LineSeriesViewModel -->
<l:FastLineSeries DependentAxis="{Binding Path=YAxis}"
IndependentAxis="{Binding Path=XAxis}"
ItemsSource="{Binding Path=Series}"/>
</DataTemplate>
<Style TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<!--My stacked chart control -->
<l:StackedPanel x:Name="stackedPanel" Width="Auto" Height="Auto" Background="LightBlue">
</l:StackedPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ClipToBounds="True">
<!-- View is an ObservableCollection of all axes and series-->
<ItemsControl x:Name="chartItems" ItemsSource="{Binding Path=View}" Focusable="False">
</ItemsControl>
</Grid>
This code works quite well. When I add axes they get drawn. But the DependentAxis and InDependentAxis of the lineseries control stay null, so the series doesn't get drawn. How can I bind the nested viewmodels to the properties of a control?
It should work. A few things you can check:
Does the Series Binding work? If so, try to figure out what's the difference.
Are you sure that the XAxis and YAxis properties actually have values? Try putting a breakpoint in the getter. If it's reached, the Binding works. You can also put a converter (IValueConverter) on the Binding (that simply returns the value it receives) and place a breakpoint there.
Use PresentationTraceSources.TraceLevel=High on the Binding to get more verbose tracing (that will appear in the VS Output window).
Are DependentAxis/IndependentAxis defined as dependency properties on FastLineSeries?
Hope that helps,
Aelij.
You've probably already checked this but I find that when I'm debugging bindings the first and easiest place to start is running a debug session from VS as the debug output tells which objects and properties are failing to bind. I usually end up discovering I need to explicitly set a DataContext or something else like a typo. The output to look for start like this:
System.Windows.Data Error: 39 :
BindingExpression path error:
this is followed by the property name you tried to bind to and usually most importantly the class against which its actually trying to bind. If this doesn't help there's a great article here on the debugging bindings: http://www.beacosta.com/blog/?p=52 which discusses the use of PresentationTraceSources.TraceLevel=High which Aelij mentioned, as well as a few other techniques. Hope this gets onto the right track.
Regards,
Mike

Categories

Resources