WPF - Draw elements and connectors on Canvas with DataBinding - c#

I am working on a WPF node/graph view application. Each node has a fixed (computed) position, some contents like a text, and a list of children. As soon as all the positions are recalculated, I would like to draw these nodes on a Canvas and draw the connections from each node to its children.
What I have
I have followed the advice here:
WPF draw lines between elements on canvas in a itemscontrol
I am keeping two collections, one for the nodes and one for the connectors between them, and binding them as a CompositeCollection.
Here is the relevant XAML:
<ItemsControl x:Name="Collection">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:Node}">
<TextBox Text="{Binding Text}" Width="160" Height="100"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Connector}">
<Line Stroke="Black" StrokeThickness="1"
X1="{Binding From.X}" Y1="{Binding From.Y}"
X2="{Binding To.X}" Y2="{Binding To.Y}" />
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
Here are my data structures.
public class Node
{
public string Text { get; set; }
public double X { get; set; }
public double Y { get; set; }
public List<Node> Children;
public Point RightConnector => new Point(X + 160, Y + 50);
public Point LeftConnector => new Point(X, Y + 50);
}
public class Connector
{
public Point From { get; set; }
public Point To { get; set; }
}
In code: For each node, I am iterating over the children and explicitly creating a Connector for each. I am filling the two ObservableCollections, adding them to a CompositeCollection and telling the XAML about it using Collection.ItemsSource = ObjectCollection.
What I'm missing
While this works, I would much rather do the following:
Create a master ObservableCollection<Node> where I keep all the nodes.
On each node, have another ObservableCollection<Node> storing references to its children (could also be ObservableCollection<string> or ObservableCollection<uint32> using node name/ID - this is how the data will be stored on disk in the end; whichever way, I am able to access the node's children and their positions).
Bind the master ObservableCollection<Node> from step 1 to the ItemsControl.
For each item of the collection, let WPF draw both the node representation and the variable number of connectors to the node's n children. Note that in this scenario the Connector class doesn't exist anymore, but a connector is implicitly defined by the source Node's position and a child's position.
How can I make WPF react to a single collection item by drawing both the node itself and its connectors?

The short answer is by using a hierarchicaldatatemplate.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.hierarchicaldatatemplate?view=windowsdesktop-7.0
There will be some side effects to this though.
Your children will be in the same itemcontainer.
I'm not so sure this is a better plan.
EG In my map editor. My trees are in the same observablecollection as the woods which creates their outline. Trees are logically children of the woods they are "inside" but each wood and tree viewmodel is "just" another object in my bound collection below. A WoodVM also has an ObservableCollection for it's "children".
https://imgur.com/u720QD5
Different terrain types have different child symbols.
Water has waves and the occasional fish, Swamp has marsh grass symbols. Etc.
You realise, BTW, you can bind an Observablecollection rather than compositecollection?

I would like to present 2 ways to solve the problem. As a first step I will present the Node class which has "Children" property to child Nodes and also a Parent point to the parent Node. The Parent Property can be useful for drawing the line.
Here is the Node class
public class Node :Binding
{
public Node()
{
Children = new ObservableCollection<Node>();
}
public Node(string text,double x,double y )
{
Text = text;
X = x;
Y = y;
Children = new ObservableCollection<Node>();
}
Node _parent;
public Node Parent
{
get {
if (_parent == null)
{
return this;
}
return _parent; }
set { _parent = value;NotifyPropertyChanged(nameof(Parent)); }
}
string _text;
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged(nameof(Text)); }
}
double _x;
public double X
{
get { return _x; }
set { _x = value;NotifyPropertyChanged(nameof(X)); }
}
double _y;
public double Y
{
get { return _y; }
set { _y = value; NotifyPropertyChanged(nameof(Y)); }
}
ObservableCollection<Node> _children;
public ObservableCollection<Node> Children
{
get { return _children; }
set { _children = value;NotifyPropertyChanged(nameof(Children)); }
}
public void Add(Node child)
{
Children.Add(child);
child.Parent = this;
}
}
It seems to me that the combination of ItemsControl and the HierarchicalDataTemplate is not working. If you want to use the HierarchicalDataTemplate please use the TreeView control as following:
<Grid>
<TreeView x:Name="Collection" ItemsSource="{Binding Nodes}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Node}" ItemsSource="{Binding Path=Children}" >
<TextBox Text="{Binding Text}" Width="160" Height="100" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
This is very simple but I don't know if it matches your needs.
Alternatively you can re-build in code the Canvas from scratch on any change. I do not have a code for you .
The other solution I have is a recursive use of user control.
and this is its use:
<Grid>
<ItemsControl ItemsSource="{Binding Nodes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:RecursiveSolution/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
The ReursiveSolution User Control is as following:
<Canvas>
<TextBox Text="{Binding Text}" Width="160" Height="100" Canvas.Left="{Binding X}" Canvas.Top="{Binding Y}"/>
<Line X1="{Binding X}" Y1="{Binding Y}" X2="{Binding Parent.X}" Y2="{Binding Parent.Y}" Stroke="Black"/>
<ItemsControl x:Name="Collection" ItemsSource="{Binding Children}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:Node}">
<Grid>
<local:RecursiveSolution/>
</Grid>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</Canvas>
As one can see, I draw the line. The line is not as requested because I was too lazy to go all the way. But this is simple using some converters.
Due to the recursive solution the Canvas areas are placed one on top of its parent.
I do not see a real problem , but we may need an improvement here.
Hope it helps

Related

WPF TreeView doesn't display arrows unless I modify styling during runtime

I have an ObservableCollection that I populate during runtime which is bound to the TreeView. When the collection is updated, the root object of the collection appears in the TreeView without any way to expand it (see the first image).
I assumed this meant there was an issue with the binding, however, removing TreeView.ItemCotainerStyle tag makes the arrow appear and everything works as intended (see the second image). This behaviour works in reverse too, if the style tag isn't in the view and I add it after the collection is updated then the arrows will appear.
The style tags aren't necessary for any function in my project to work, they're just leftover from an example I was working from.
<TreeView ItemsSource="{Binding CompileMessages}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight"
Value="Normal" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type models:CompileMessagesDto}"
ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="{Binding Path=State}"
Margin="3"
Foreground="White" />
<TextBlock Text="{Binding Path=Path}"
FontWeight="Normal"
Foreground="White"
FontSize="12"
Margin="3" />
<TextBlock Text="{Binding Path=Description}"
FontWeight="Normal"
FontSize="12"
Foreground="#ff8000"
Margin="3" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
public class CompileMessagesDto
{
public string State { get; set; } = "";
public string Path { get; set; } = "";
public string Description { get; set; } = "";
public string Parent { get; set; }
public ObservableCollection<CompileMessagesDto> Children { get; set; } = new ObservableCollection<CompileMessagesDto>();
}
TreeView before modification
TreeView after removing the style tag
Initialising the collection with test values displays the arrows, it's only after the collection is modified during runtime that the TreeView behaves like this. I use MaterialDesignInXaml if that helps at all.
Edit (how the collection is updated)
It's definitely not the best way to do it but I loop through a collection of compiler messages and add them to the collection. I'm left with every object and a reference to their parent.
foreach (CompilerResultMessage message in messages)
{
CompileMessagesDto compileMessage = new CompileMessagesDto { State = message.State.ToString(), Path = message.Path, Description = message.Description, Parent = parent };
MessageCrawl(message.Messages, message.Path);
CompileMessages.Add(compileMessage);
}
Then I set each object's children, and remove every object from the collection that isn't the root object. Leaving the root with the tree of children in it.
List<CompileMessagesDto> removeItems = new List<CompileMessagesDto>();
foreach (CompileMessagesDto message in CompileMessages)
{
message.Children = new ObservableCollection<CompileMessagesDto>(CompileMessages.Where(c => c.Parent == message.Path));
if (message.Parent != "Root") { removeItems.Add(message); }
}
foreach (CompileMessagesDto message in removeItems)
{
CompileMessages.Remove(message);
}
You are overwriting the binding source CompileMessagesDto.Children:
message.Children = new ObservableCollection<CompileMessagesDto>(...);
Since CompileMessagesDto doesn't implement INotifyProeprtyChanged the Binding won't be able to recognize the property change.
General rule of data binding: the binding source must always implement it's properties as DependencyProperty if the source is a subclass of DependencyObject. Otherwise the source object must implement INotifyPropertyChanged.
If you don't do this, your will create potential memory leaks.
To solve your problem either let CompileMessagesDto implement INotifyPropertyChanged and raise the PropertyChanged event from the CompileMessagesDto.Children property (recommended solution).
Alternatively keep the current ObservableCollection instance and use Add and Remove to modify it.
// Using for-statement allows to get rid of the 'removedItems' collection and the second foreach-statement (improve performance)
for (int index = CompileMessages.Count - 1; index >= 0; index--)
{
CompileMessages
.Where(c => c.Parent == message.Path)
.ToList()
.ForEach(message.Children.Add);
if (message.Parent != "Root")
{
CompileMessages.Remove(message);
}
}
Also note that your Style does throw errors. CompileMessagesDto does neither have a IsExpanded nor a IsSelected property.

Populate a Tree View with a Custom List Object

I have a custom object that consists of 2 properties. The first is a string that i wish to use as a summary or header in the tree view. The second is a list of a custom type that contains objects that are to be included under each header. The objects contain things such as name, id, area, etc.. Ill most likely default to the name property of those list objects. How can I push this into a tree view.
Concatenated Model
public class WVWellModel : Notifier
{
private string _API;
public string API
{
get
{
return this._API;
}
set
{
this._API = value; OnPropertyChanged("API");
}
}
private string _WellName;
public string WellName
{
get
{
return this._WellName;
}
set
{
this._WellName = value; OnPropertyChanged("WellName");
}
}
private string _Division;
public string Division
{
get
{
return this._Division;
}
set
{
this._Division = value; OnPropertyChanged("Dvision");
}
}
private string _Area;
public string Area
{
get
{
return this._Area;
}
set
{
this._Area = value; OnPropertyChanged("Area");
}
}
private string _FieldOffice;
public string FieldOffice
{
get
{
return this._FieldOffice;
}
set
{
this._FieldOffice = value; OnPropertyChanged("FieldOffice");
}
}...............
** Model that will be put in a list to be injected into tree view**
public class groupingModel : Notifier
{
private string _Header;
public string Header
{
get { return _Header; }
set { _Header = value; OnPropertyChanged("Header"); }
}
private List<WVWellModel> _Wells;
public List<WVWellModel> Wells
{
get { return _Wells; }
set { _Wells = value; OnPropertyChanged("Wells"); }
}
}
List of Custom Type to be injected into tree view
List treeViewList = someMethod();
In summary, I would like to bind my tree view to a custom list object.List<groupingModel> The object in those lists have two properties, a string header that is to be used to group the objects in the tree view, and a second property that contains a list of custom objects "WVWellModel".
EDIT TO XAML to Allow Selection of all items in group
I've attempted to go ahead and make the group selectable with he goal that if the group is selected all children are selected underneath. Ive successfully bound it to a property inside of the group called "IsChecked". it defaults to false and works successfully. The problem is i am unable to capture the change in value and thus cannot run any logic to select its children.
<TreeView DockPanel.Dock="Bottom" ItemsSource="{Binding Groups}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="wellModel:WellGroupModel" ItemsSource="{Binding Wells}">
**<CheckBox Content="{Binding Header}" IsChecked="{Binding IsChecked}"/>**
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type wellModel:WellModel}">
<CheckBox Content="{Binding WellName}" IsChecked="{Binding IsSelected}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The TreeView control uses HierarchicalDataTemplate to control how items are displayed and how their children are populated. If your item class has children of a different type, it can specify its own child ItemTemplate, and so on recursively.
I've also added a minimal top-level viewmodel which owns a collection of GroupingModel. I'm using conventional C# naming conventions: Classes and properties start with a capital letter, private fields start with an underscore and a lower-case letter. It seems silly but when everybody uses the same convention, you always know what you're looking at.
Finally, I used ObservableCollection<T> rather than List<T>. If you bind an ObservableCollection to a control, then you can add/remove items in the collection and the control will automatically be notified and update itself without any additional work on your part.
XAML
<TreeView
ItemsSource="{Binding Groups}"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:GroupingModel}"
ItemsSource="{Binding Wells}"
>
<TextBlock Text="{Binding Header}" />
<HierarchicalDataTemplate.ItemTemplate>
<!-- This can be DataTemplate if no child collection is specified -->
<DataTemplate
DataType="{x:Type local:WVWellModel}"
>
<TextBlock Text="{Binding WellName}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Alternatively, if you have heterogeneous collections of objects, you can create implicit templates as resources and let them be applied by type rather than by hierarchy. In your particular case, this will produce identical results, because you have a strict item hierarchy.
<TreeView
ItemsSource="{Binding Groups}"
>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:GroupingModel}"
ItemsSource="{Binding Wells}"
>
<TextBlock Text="{Binding Header}" />
</HierarchicalDataTemplate>
<DataTemplate
DataType="{x:Type local:WVWellModel}"
>
<TextBlock Text="{Binding WellName}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
C#
public class ViewModel : Notifier
{
public ViewModel()
{
Groups = new ObservableCollection<GroupingModel>
{
new GroupingModel {
Header = "First Group",
Wells = new List<WVWellModel> {
new WVWellModel() { WellName = "First Well" },
new WVWellModel() { WellName = "Second Well" },
new WVWellModel() { WellName = "Third Well" },
}
},
new GroupingModel {
Header = "Second Group",
Wells = new List<WVWellModel> {
new WVWellModel() { WellName = "Third Well" },
new WVWellModel() { WellName = "Fourth Well" },
new WVWellModel() { WellName = "Fifth Well" },
}
}
};
}
#region Groups Property
private ObservableCollection<GroupingModel> _groups = new ObservableCollection<GroupingModel>();
public ObservableCollection<GroupingModel> Groups
{
get { return _groups; }
set
{
if (value != _groups)
{
_groups = value;
OnPropertyChanged(nameof(Groups));
}
}
}
#endregion Groups Property
}
Update
Let's make the WVWellModel items checkable. First, we'll give them a boolean property that we'll bind to the checkbox's IsChecked property:
public class WVWellModel : Notifier
{
private bool _isSelected;
public bool IsSelected
{
get
{
return this._isSelected;
}
set
{
this._isSelected = value; OnPropertyChanged();
}
}
And then we'll change the content in the WVWellModel DataTemplate from a TextBlock to a CheckBox:
<DataTemplate
DataType="{x:Type local:WVWellModel}"
>
<CheckBox
Content="{Binding WellName}"
IsChecked="{Binding IsSelected}"
/>
</DataTemplate>
You can put any valid XAML UI in a template as long as there's a single root element.
<TreeView
Width="300"
Height="200"
ItemsSource="{Binding Groups}"
Grid.IsSharedSizeScope="True"
>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:GroupingModel}"
ItemsSource="{Binding Wells}"
>
<TextBlock Text="{Binding Header}" />
</HierarchicalDataTemplate>
<DataTemplate
DataType="{x:Type local:WVWellModel}"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="CheckBoxColumn" />
<ColumnDefinition Width="Auto" SharedSizeGroup="APIColumn" />
</Grid.ColumnDefinitions>
<CheckBox
Grid.Column="0"
Content="{Binding WellName}"
IsChecked="{Binding IsSelected}"
/>
<TextBlock
Grid.Column="1"
Margin="12,0,0,0"
Text="{Binding API}"
HorizontalAlignment="Right"
/>
</Grid>
</DataTemplate>
</TreeView.Resources>
</TreeView>

Binding to a TextBox inside an ItemsControl ItemTemplate

The binding on my ItemsControl ItemsTemplate does not work. I've gone through some other similar stack overflow questions but I cannot figure out the problem with my binding. Can someone please show me what I am doing wrong with my binding?
Excerpt from my MainWindow's ViewModel;
private ObservableCollection<uint> words;
private uint word;
private int numberOfWords;
public ObservableCollection<uint> Words
{
get
{
return this.words;
}
set
{
this.words = value;
this.NotifyPropertyChanged(m => m.Words);
}
}
public uint Word
{
get
{
return this.word;
}
set
{
this.word = value;
this.NotifyPropertyChanged(m => m.Word);
}
}
public int NumberOfWords
{
get
{
return this.numberOfWords;
}
set
{
this.numberOfWords = value;
this.NotifyPropertyChanged(m => m.NumberOfWords);
this.Words.Clear();
for (uint x = 0; x < value; x++)
{
this.Words.Add(this.Word);
}
}
}
I have the below ItemsControl inside a user control. The MainWindow has its DataContext set to a ViewModel, which the ItemsControl uses. The ItemsSource binding works and I get however many textboxes I specify, but when putting a value in the TextBox, the binding does not work.
<ItemsControl Grid.Row="0" Grid.Column="1" Grid.RowSpan="8" ItemsSource="{Binding Words}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Word}" Width="125" Height="25" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I saw one post that talks about using this type of binding below, but apparently, I do not understand FindAncestor, so I do not know if I am on the right track or not with this.
Text="{Binding Path=Word, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"
You cannot bind to elements in a collection and then change the element itself--you can only change properties of this element through the binding. In other words, given the following collection
[ "one", "two", "three" ]
through a binding such as
<TextBox Text="{Binding Words[0]} /> <!-- the word "one" is displayed in the tb -->
if you change "one" to "derp" you would not alter the collection to
[ "derp", "two", "three" ]
To apply this to your example, you would want to bind the collection to the ItemsControl, then in the template bind to each instance within the collection and change properties of this instance.
First, create your Model. This holds your data and is what you bind against in the UI.
public sealed class Word
{
public uint Value {get;set;}
}
Next, expose a collection of these on your View Model.
public sealed class ViewModel
{
//create and fill in ctor
public ObservableCollection<Word> WordsYo {get;private set;}
}
Next, bind your ItemsControl's ItemsSource to this property and bind elements in the template to the properties of Word:
<!-- Window.DataContext is set to an instance of ViewModel -->
<ItemsControl ItemsSource="{Binding WordsYo}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The Aristocrats.

Width="*" in longlistselector giving error "Error HRESULT E_FAIL has been returned from a call to a COM component"

I used DataTemplateSelector example as shown in this page and modified it a little to suit my requirements as shown below.
public abstract class TemplateSelector : ContentControl
{
public abstract DataTemplate SelectTemplate(object item, DependencyObject container);
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
var parent = GetParentByType<LongListSelector>(this);
ContentTemplate = SelectTemplate(newContent, this);
}
private static T GetParentByType<T>(DependencyObject element) where T : FrameworkElement
{
T result = null;
DependencyObject parent = VisualTreeHelper.GetParent(element);
while (parent != null)
{
result = parent as T;
if (result != null)
{
return result;
}
parent = VisualTreeHelper.GetParent(parent);
}
return null;
}
}
public class MyTemplateSelector : TemplateSelector
{
public DataTemplate one { get; set; }
public DataTemplate two { get; set; }
public DataTemplate three { get; set; }
public DataTemplate four { get; set; }
public DataTemplate five { get; set; }
public DataTemplate six { get; set; }
public DataTemplate seven { get; set; }
public DataTemplate eight { get; set; }
public DataTemplate nine { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
PoiData poiItem = item as PoiData;
if (poiItem != null)
{
switch (poiItem.Type)
{
case "atm":
return one;
case "food":
return two;
case "hospital":
return three;
case "police":
return four;
case "pharmacy":
return five;
case "gas_station":
return six;
case "hindu_temple":
return seven;
case "train_station":
return eight;
case "movie_theater":
return nine;
}
}
return null;
}
}
And my xaml looks like this.
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="one">
<StackPanel Height="75">
<Grid Height="65"
Background="Red"
HorizontalAlignment="Left"
Margin="0, 0, 0, 12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="65" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Grid VerticalAlignment="Top"
HorizontalAlignment="Left"
Width="65"
Height="65"
Background="White"
Grid.Column="0"
Margin="0, 0, 0, 0">
</Grid>
<Grid Grid.Column="1" Width="auto">
<TextBlock Text="{Binding Title}"
FontSize="30" Margin="10,0,0,0"
VerticalAlignment="Center"
Foreground="White"/>
</Grid>
</Grid>
</StackPanel>
</DataTemplate>
.
.
.
</phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="SelectingTemplate">
<local:MyTemplateSelector Content="{Binding}"
one="{StaticResource one}"
two="{StaticResource two}"
three="{StaticResource three}"
four="{StaticResource four}"
five="{StaticResource five}"
six="{StaticResource six}"
seven="{StaticResource seven}"
eight="{StaticResource eight}"
nine="{StaticResource nine}" />
</DataTemplate>
And this is the panorama item xaml
<phone:PanoramaItem Header="{Binding Path=LocalizedResources.AroundMe, Source={StaticResource LocalizedStrings}}">
<!--Double wide Panorama with large image placeholders-->
<phone:LongListSelector Margin="12,-20,0,75"
ItemsSource="{Binding Poi.Items}"
ItemTemplate="{StaticResource SelectingTemplate}"
SelectionChanged="PoiSelectionChanged" >
</phone:LongListSelector>
</phone:PanoramaItem>
My problem is, if I use this code, the stackpanel always aligns itself to the center of the panorama item no matter if I set HorizontalAlignment to left or right or stretch.
And if I set the width property of either the stackpanel or the child grid to * to take up the entire screen space, I get an error which I think is completely irrelevant.
{MS.Internal.WrappedException: Error HRESULT E_FAIL has been returned from a call to a COM component. ---> System.Exception: Error HRESULT E_FAIL has been returned from a call to a COM component.
at MS.Internal.XcpImports.CheckHResult(UInt32 hr)
at MS.Internal.XcpImports.FrameworkElement_MeasureOverride(FrameworkElement element, Size availableSize)
at System.Windows.FrameworkElement.MeasureOverride(Size availableSize)
at System.Windows.FrameworkElement.MeasureOverride(IntPtr nativeTarget, Double inWidth, Double inHeight, Double& outWidth, Double& outHeight)
--- End of inner exception stack trace ---}
What exactly am I supposed to do to set the stackpanel's width to take up the entire screen width ? Is there any workaround ?
And why is this exception throwing when I set the width to *.
Two thing to solve your problem
Remove the parent stackpanel and only have the Grid as the content of the DataTemplate
Set the Width of the second ColumnDefinition to '*'
Why does this work?
StackPanels are built to consume as little space as possible. So having a StackPanel as the "parent element" or the content of the DataTemplate makes your content "shrink" to only as much space as it is told (by setting explicit widths).
The second column of the grid was told to "auto" size. That means take up only as much space as needed to render (just like the StackPanel was doing!). Setting the size to "*" tells the columns to take as much space as available.
I found the solution! I found it Googling here: http://webcache.googleusercontent.com/search?q=cache:KDZZtrw9eG0J:y2bd.me/blog/2013/08/16/fixing-alignment-issues-with-datatemplateselector/+&cd=6&hl=en&ct=clnk&gl=us
Basically, the XAML tag that references your TemplateSelector needs to have HorizontalContentAlignment set to stretch. Something like this:
<DataTemplate x:Key="YourTemplateSelectorKey">
<namespace:YourTemplateSelectorClass HorizontalContentAlignment="Stretch" ... />
</DataTemplate>
Tada!

How bind an object to treeview from Xaml

I am trying to bind an object to the treeviewcontrol WPF by XAML, I am getting the treview as empty. When i am doing that by treeview.items.add(GetNode()) then its working.
I am using MVVM Framework(caliburn.Micro) I wanted to do it in Xaml, how do I assign Item source property in xaml? I tried with creating a property of Node class and calling the Method GetNode() with in the property, and assigned that property as itemssource of the treeview and changed the List to Observable collection. Still issue is same.
Working Xaml when doing treeview.items.Add(GetNode()) which returns a Node and and i as assigning Nodes collection to Hireachial Template.
<TreeView Name="treeview2"
Grid.RowSpan="2"
Grid.ColumnSpan="2"
ItemContainerStyle="{StaticResource StretchTreeViewItemStyle}" Width="300">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<DockPanel LastChildFill="True">
<TextBlock Padding="15,0,30,0" Text="{Binding Path=numitems}" TextAlignment="Right"
DockPanel.Dock="Right"/>
<TextBlock Text="{Binding Path=Text}" DockPanel.Dock="Left" TextAlignment="Left" />
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Server Side Code:
this.treeview2.Items.Add(GetNode());
GetNode recursively build a list of type Node.
public class Node
{
public string Text { get; set; }
public List<Node> Nodes { get; set; }
public ObservableCollection<Node> Nodes{get;set;} // with list and observable collection same results
public int numitems { get; set; }
}
In addition to the HierarchicalDataTemplate, which seems just fine, add a binding to the ItemsSource property of your TreeView:
public class ViewModel
{
private List<Node> _rootNodes;
public List<Node> RootNodes
{
get
{
return _rootNodes;
}
set
{
_rootNodes = value;
NotifyPropertyChange(() => RootNodes);
}
}
public ViewModel()
{
RootNodes = new List<Node>{new Node(){Text = "This is a Root Node}",
new Node(){Text = "This is the Second Root Node"}};
}
And in XAML:
<TreeView ItemsSource="{Binding RootNodes}"
.... />
Edit: Remove the call that does this.Treeview.... you don't need that. Try to keep to the minimum the amount of code that references UI Elements. You can do everything with bindings and have no need to manipulate UI Elements in code.

Categories

Resources