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.
Related
I have set up a ListView to manage tabs on my app. So users can drag and drop tabs to new instances of the app and reorder tabs. All goes great until I set up a ControlTemplate for the Itens at the ListView.
I used ControlTemplate because when I add a ListView.ItemTemplate directly it won`t work (it does not affect the listview item). So before adding a new item I use this:
tab.Template = (ControlTemplate)this.Resources["listViewItemTemplate"];
So the tabs is looking great, but all the functions that used to work (like drag and drop) isn't working anymore. When I remove the ControlTemplate the ListView show only one little string.
This is what is happening
My ListView Code:
<ListView x:Name="TabsListView"
CanDragItems="True"
AllowDrop="True"
DragEnter="TabsListView_DragEnter"
Drop="TabsListView_Drop"
ScrollViewer.VerticalScrollBarVisibility="Disabled
CanReorderItems="True"
DragItemsStarting="TabsListView_DragItemsStarting"
ItemClick="TabsListView_ItemClick" >
I know for sure that the issue isn't on the drag and drop method.
To create a new item I just use Add. Of course I checked every single item inside ControlTemplate to know that it is not blocking anything.
The control template is:
<ControlTemplate x:Key="listViewItemTemplate" >
<ListViewItem>
<Grid Tapped="Grid_Tapped" Width="180" Margin="-12,-12,-12,0">
<TextBlock Text="{Binding Name}" Margin="5,6,0,0"></TextBlock>
</Grid>
</ListViewItem>
</ControlTemplate>
The reason is that you are using ControlTemplate and embedding a ListViewItem in there. ListViewItems are created automatically by the control to contain the items, and ItemTemplate is a template of the contents of this container. So it should look like this:
<DataTemplate x:Key="listViewItemTemplate" >
<Grid Tapped="Grid_Tapped" Width="180" Margin="-12,-12,-12,0">
<TextBlock Text="{Binding Name}" Margin="5,6,0,0"></TextBlock>
</Grid>
</ControlTemplate>
<ListView ...
ItemTemplate="{ThemeResource listViewItemTemplate}">
...
</ListView>
Furthermore, if you want to style the ListViewItem (the container), you can create a style with TargetType="ListViewItem" and set it as ListView.ItemContainerStyle.
<Style TargetType="ListViewItem" x:Key="TabListViewItemContainerStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Grid x:Name="ContentBorder">
... some template with ContentPresenter:
<ContentPresenter x:Name="ContentPresenter" ... />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ListView ...
ItemContainerStyle="{StaticResource TabListViewItemContainerStyle}">
...
</ListView>
You can find the default style in: C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.15063.0\Generic
Update
I have checked the code and found the problem. What you are actually doing is adding ListViewItems directly in code:
ListViewItem tab = new ListViewItem();
DataModel model = new DataModel();
model.Name = $"NewTab {counter}";
tab.DataContext = model;
tab.Content = model.ToString();
TabsListView.Items.Add(tab);
Unfortunately this does not work, because when ListView sees an item that is of type ListViewItem, it just adds it to the list, but ItemTemplate doesn't apply to it, because that is applied only when the items are not UIElement but rather a custom class. If you use DataModel directly, ItemTemplate will be applied as expected:
DataModel model = new DataModel();
model.Name = $"NewTab {counter}";
TabsListView.Items.Add(model);
ListView encounters DataModel, knows that just a class not a UIElement, so it internally creates a ListViewItem, applies ItemContainerStyle to it and then uses the ItemTemplate to create the UI that will be displayed inside the ListViewItem's ContentPresenter. It is better to use this approach as it gives you better decoupling as you don't have to create UI-based classes in the code-behind and you get more control (as you can change both the style of the item as well as the container).
I would like to create a custom control that provides all the functionality of the DockPanel, but it also exposes a secondary Overlay that is "outside" of the DockPanel. There would be a dependency property that will control the visibility of the the overlay panel, such that when the property is set true/visible, the Panel will appear overlayed on top of everything within the DockPanel.
Ideally the consumer would be able to drop the control into the same situation as a normal DockPanel, and with no other changes it would behave just like the normal DockPanel:
<DockPanelWithOverlay LastChildFill="True" >
<Button DockPanel.Dock="Bottom".../>
<Button DockPanel.Dock="Top".../>
<Grid>
<Grid controls.../>
</Grid>
</DockPanelWithOverlay>
However, there would be available the secondary area into which they could place the additional content and invoke when required.
<DockPanelWithOverlay LastChildFill="True" >
<Button DockPanel.Dock="Bottom".../>
<Button DockPanel.Dock="Top".../>
<Grid>
<Grid controls.../>
</Grid>
<DockPanel.Overlay>
<whatever controls for the overlay>
</DockPanel.Overlay>
</DockPanelWithOverlay>
But that wouldn't be valid since the Content is being set twice? So to cope, when using the overlay I guess I would have to explicitly state what goes where?:
<DockPanelWithOverlay LastChildFill="True" >
<DockPanel.Children>
<Button DockPanel.Dock="Bottom".../>
<Button DockPanel.Dock="Top".../>
<Grid>
<Grid controls.../>
</Grid>
</DockPanel.Children>
<DockPanel.Overlay Visibility="{Binding IsVisible}">
<whatever controls for the overlay>
</DockPanel.Overlay>
</DockPanelWithOverlay>
I'm not entirely sure the best way to tackle this: whether to create a CustomControl, or a UserControl, inherit directly from the DockPanel and try to expose a separate ContentControl, or maybe inherit from Panel and delegate the MeasureOverride and ArrangeOverride to the DockPanel.
How should I tackle this?
Interesting question. I wrote a DockPanelWithOverlay component that does the work:
I chose here the CustomControl because I wanted to have inheritance of Panel.
But Panel doesn't have a template it can change.
So I wrote a Custom Control inheriting of Control with a custom template
But a Usercontrol would perfectly work I think (I didn't try to be honest)
Edit UserControl is not so good, because it inherits of ContentControl.
So it can only have one children.
The goal of the DockPanelWithOverlay is to have many children.
So I think UserControl is not the best inheritance, as often.
UserControl is better when you want to provide some content in xaml, mostly static, not customizable by user of control.
End of edit
To organize content inside the tempalte, I used a Grid.
Order of the two components matters.
It is the drawing order.
Grid allows to put two components at the same place :
Inside there'll be the Overlay control, and a underlying DockPanel.
DockPanelWithOverlay
..|
..|-ControlTemplate
......|
......|-Grid
..........|
..........|--DockPanel
..........|--OverlayControl
Having a template is easier to make some binding from the DockPanelWithOverlay to the template's controls properties.
(To generate a CustomControl, create a WPFCustom Control Library Project)
Excerpt of themes\generic.xaml in library :
<Style TargetType="{x:Type local:DockPanelWithOverlay}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DockPanelWithOverlay}">
<!-- the grid allows to put two components at the same place -->
<Grid >
<DockPanel x:Name="dockPanel" />
<ContentControl x:Name="overlayControl" Visibility="{TemplateBinding OverlayVisibility}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Inheriting of control allows to use the template to create the small UIElements hierarchy.
Some dependency properties must be added for allowing binding :
Overlay for providing some UIElements, or a string for overlay content
OverlayVisibility for hiding/showing the overlay
Here is the code for the DockPanelWithOverlay :
(Note the OnApplytemplate called just after the templates componenets are called)
// Children is the property that will be valued with the content inside the tag of the control
[ContentProperty("Children")]
public class DockPanelWithOverlay : Control
{
static DockPanelWithOverlay()
{
// Associate the control with its template in themes/generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(typeof(DockPanelWithOverlay), new FrameworkPropertyMetadata(typeof(DockPanelWithOverlay)));
}
public DockPanelWithOverlay()
{
Children = new UIElementCollection(this, this);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// once the template is instanciated, the dockPanel and overlayCOntrol can be found from the template
// and the children of DockPanelWithOverlay can be put in the DockPanel
var dockPanel = this.GetTemplateChild("dockPanel") as DockPanel;
if (dockPanel != null)
for (int i = 0; i < Children.Count; )
{
UIElement elt = Children[0];
Children.RemoveAt(0);
dockPanel.Children.Add(elt);
}
}
// Here is the property to show or not the overlay
public Visibility OverlayVisibility
{
get { return (Visibility)GetValue(OverlayVisibilityProperty); }
set { SetValue(OverlayVisibilityProperty, value); }
}
// Here is the overlay. Tipically it could be a Texblock,
// or like in our example a Grid holding a TextBlock so that we could put a semi transparent backround
public Object Overlay
{
get { return (Object)GetValue(OverlayProperty); }
set { SetValue(OverlayProperty, value); }
}
// Using a DependencyProperty as the backing store for OverlayProperty.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty OverlayProperty =
DependencyProperty.Register("Overlay", typeof(Object), typeof(DockPanelWithOverlay), new PropertyMetadata(null));
public static readonly DependencyProperty OverlayVisibilityProperty =
DependencyProperty.Register("OverlayVisibility", typeof(Visibility), typeof(DockPanelWithOverlay), new PropertyMetadata(Visibility.Visible));
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public UIElementCollection Children
{
get { return (UIElementCollection)GetValue(ChildrenProperty); }
set { SetValue(ChildrenProperty, value); }
}
public static readonly DependencyProperty ChildrenProperty =
DependencyProperty.Register("Children", typeof(UIElementCollection), typeof(DockPanelWithOverlay), new PropertyMetadata(null));
}
Using the DockPanelWithOverlay :
<lib:DockPanelWithOverlay x:Name="dockPanelWithOverlay1"
OverlayVisibility="Visible"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Button Content="Top" Height="50" DockPanel.Dock="Top" Background="Red"/>
<Button Content="Bottom" Height="50" DockPanel.Dock="Bottom" Background="Yellow"/>
<Button Content="Left" Width="50" DockPanel.Dock="Left" Background="Pink"/>
<Button Content="Right" Width="50" DockPanel.Dock="Right" Background="Bisque"/>
<Button Content="Center" Background="Azure"/>
<lib:DockPanelWithOverlay.Overlay>
<Grid Background="#80404080">
<TextBlock Text="Overlay" FontSize="80" Foreground="#FF444444" HorizontalAlignment="Center" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="-15"/>
<TranslateTransform/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</Grid>
</lib:DockPanelWithOverlay.Overlay>
</lib:DockPanelWithOverlay>
The overlay can easily be switched on or off binding from a CheckBox.IsChecked property for instance.
Here is the full working code : http://1drv.ms/1NfCl9z
I think it 's really the answer to your question. Regards
I suggest we should try to clarify how you see this working. My guess is that the secondary panel will be a DockPanel too, and will completely cover the main panel. I.e., you see either the one or the other, but never both. How do you envisage switching between the two? A ToggleButton perhaps? Or only under control of, say, some Trigger?
My first thought as to implementation is that you seem to like how DockPanel? lays things out, so why touch the layout methods? One way might be to have only one dockpanel, but two collections of children, which you set according to which you want to show. Or the secondary panel in a `Popup?
Do you want to be able to write something like this:
<DockPanelWithAlternative
AlternativeVisibility="{Binding somethingHere}" >
<TextBlock Dock.Top ... />
<TextBlock Dock.Alternative.Top ... />
</DockPanelWithAlternative>
What I am thinking of is something like:
<UserControl>
<Grid>
<DockPanel x:Name="MainPanel" ZIndex="0"/>
<DockPanel x:Name="AlternativePanel" Visbility=... ZIndex="1"/>
</Grid>
</UserControl>
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.
In GDI+/WinForms, I could do this in the Click() event and using the graphics Object:
AddPoint(p); //Add Point contains some code to make sure there is only 3 dots
foreach (Point p in PointList) {
DrawRectangle(p);
}
Invalidate();
If I try something similar in WPF, it won't cleanup the dots I created (I'm guessing because of how WPF works). What this means is if I check to make sure there is only three dots at a time, and pop off the oldest point to make room for the new one, the rectangle drawn would be still there.
So the question is, how can I create something in WPF that allows me to
Draw a rectangle at a Point
Remove rectangles/points from a WPF canvas after there is more than 3
You're doing WPF the WinForms way. Don't do that. It's like writing VB code in C++. It can only end in tears.
To do this the WPF way, use databinding and a view model class to do the logic of "no more than 3 at a time." Then, for the UI, just bind to the PointList in your view model.
Here's what my XAML should look like. Notice I'm just using an ItemsControl and a Canvas, then binding the ItemsSource to PointList:
<Window x:Class="WpfTester.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ItemsControl ItemsSource="{Binding Path=PointList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="Red" Width="25" Height="25" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Window>
Then we just need to create the PointList. We'll use the normal WPF means: a view model class to hold the point list:
class MainViewModel
{
public MainViewModel()
{
PointList = new ObservableCollection<Point>();
// Some example data:
AddPoint(new Point(10, 10));
AddPoint(new Point(200, 200));
AddPoint(new Point(500, 500));
}
public ObservableCollection<Point> PointList { get; private set; }
public void AddPoint(Point p)
{
// 3 at most, please!
if (PointList.Count == 3)
{
PointList.RemoveAt(0);
}
PointList.Add(p);
}
}
Piece of cheese, yeah? So the final part is just telling the XAML to load your view model. Inside the the code-behind for your XAML, set the DataContext to your view model:
// Inside MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
// Add this line:
this.DataContext = new MainViewModel();
}
Now that you've got that in place, you can add/remove rectangles anywhere in your code simply by calling viewModel.AddPoint or viewModel.PointList.Remove, and the UI will automatically update to reflect the changes.
I would use wpf data binding to bind the content of the canvas to a collection of rectangles you store elsewere. You need to learn databinding anyway if you want to do serious WPF development.
Edit: Of course you store just the Rectangles, the data binding should create a shape for each rectangle.
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