ItemsControl items positions issue - c#

Introduction
Hello!
I'm in the process of creating a custom WPF UserControl to display linked nodes (specifically for neural network visualization). I want to provide the UserControl's object source through data binding.
Code
XAML
<Grid Width="{Binding Width}" Height="{Binding Height}">
<StackPanel>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- Node container -->
<Canvas Background="AliceBlue"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding PosX}"/>
<Setter Property="Canvas.Top" Value="{Binding PosY}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<!-- The Node -->
<Ellipse Width="{Binding Radius}" Height="{Binding Radius}" Fill="{Binding BackColor}" />
<!-- Node Links -->
<ItemsControl ItemsSource="{Binding Links}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line Stroke="Black" StrokeThickness="2"
ToolTip="{Binding Tooltip}"
X1="{Binding SourceX}" Y1="{Binding SourceY}"
X2="{Binding DestinationX}" Y2="{Binding DestinationY}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel> </Grid>
Csharp
public class StructureViewModel : BaseViewModel
{
public double Width { get; set; } = 500;
public double Height { get; set; } = 500;
public List<Node> Items { get; set; }
public Canvas SelectedShape { get; set; }
public StructureViewModel()
{
Items = new List<Node>();
var node1 = new Node(100, 150);
var node2 = new Node(100, 180);
node1.AddLink(node2);
Items.Add(node1);
Items.Add(node2);
}
}
public class Node : BaseViewModel
{
public double Radius { get; set; } = 40;
public Brush BackColor { get; set; } = Brushes.Blue;
public double PosX { get; set; } = 0;
public double PosY { get; set; } = 0;
public List<Link> Links { get; set; }
public Node(double posX, double posY)
{
Links = new List<Link>();
PosX = posX;
PosY = posY;
}
public Node(double posX, double posY, List<Link> links)
{
Links = links;
PosX = posX;
PosY = posY;
}
public void AddLink(Node node) => Links.Add(new Link(PosX, PosY, node.PosX, node.PosY));
}
public class Link : BaseViewModel
{
public double SourceX { get; set; }
public double SourceY { get; set; }
public double DestinationX { get; set; }
public double DestinationY { get; set; }
public string ToolTip { get; set; }
public Link(double x1, double y1, double x2, double y2, string tooltip = "")
{
SourceX = x1;
SourceY = y1;
DestinationX = x2;
DestinationY = y2;
ToolTip = tooltip;
}
}
The BaseViewModel implements the property change event through Fody Weaver.
The issue
The Nodes and links are created as expected, although the position of the linking line is incorrect. The linking line is supposed to start from node1 position and end at node2 position.
Tried but failed
I've tried setting the Style TargetType to the Canvas, Grid and other targets, but none worked. Creating a style for the Grid in the DataTemplate didn't help either.
Wrapping up
Please help me solve this issue and most importantly better understand what I was doing wrong. Please let me know if you need any additional information.
Thank you!

Related

Why aren't my line showing up on my ItemsControl?

I'm attaching an ObservableCollection to it filled with a class I created. The ObservableCollection begins empty but is filled once the file is loaded. I've checked and it's properly loading the CanvasStart and CanvasEnd values. As far as I was aware the ItemsControl should be updating itself when the points are added to the ObservableCollection. See below:
my CanvasLine class:
public class CanvasLine
{
public Point DxfStart { get; set; }
public Point DxfEnd { get; set; }
public Point CanvasStart { get; set; }
public Point CanvasEnd { get; set; }
public string Layer { get; set; }
public List<double> Extents { get; set; }
public double Scale { get; set; }
public double CanvasWidth { get; set; }
public double CanvasHeight { get; set; }
public CanvasLine(Point dxfStart, Point dxfEnd, string layer)
{
DxfStart= dxfStart;
DxfEnd= dxfEnd;
Layer = layer;
}
public void GetCanvasLines(List<double> extents, double canvasWidth, double canvasHeight)
{
Extents = extents;
CanvasWidth = canvasWidth;
CanvasHeight = canvasHeight;
CanvasStart = DXFToCanvas.DXFCoordToCanvas(DxfStart, Extents, CanvasWidth, CanvasHeight);
CanvasEnd = DXFToCanvas.DXFCoordToCanvas(DxfEnd, Extents, CanvasWidth, CanvasHeight);
}
public partial class MainPage : Page
{
private List<double> extents;
private double scale;
private ObservableCollection<CanvasLine> canvasLines = new ObservableCollection<CanvasLine>();
public List<double> Extents
{
get { return extents; }
set
{
extents = value;
}
}
public double Scale
{
get { return scale; }
set { scale = value; }
}
public ObservableCollection<CanvasLine> CanvasLines
{
get { return canvasLines; }
set
{
canvasLines = value;
}
}
public MainPage()
{
InitializeComponent();
DataContext = this;
}
public void LoadCanvasDXF()
{
extents = LoadDXF.LoadExtents(Globals.CAD_FILE_PATH);
scale = LoadDXF.GetScale(extents, MainPageCanvas.Height, MainPageCanvas.Width);
foreach (CanvasLine canvasLine in Globals.LINE_LIST)
{
canvasLine.GetCanvasLines(Extents, MainPageCanvas.Width, MainPageCanvas.Height);
CanvasLines.Add(canvasLine);
}
}
}
XAML for the ItemsControl:
<ItemsControl ItemsSource="{Binding CanvasLines}" x:Name="MainPageCanvas" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Width="600" Height="300">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="White" Width="600" Height="300"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding CanvasStart.X}" Y1="{Binding CanvasStart.Y}"
X2="{Binding CanvasStart.X}" Y2="{Binding CanvasStart.Y}"
Stroke="Black" StrokeThickness="3"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is an example of a few of the lines start and end:
CanvasStart: 428.88814432501835, 266.5502781209483 CanvasEnd: 349.5791140550308, 266.5502781209483
CanvasStart: 349.5791140550308, 268.2016894748275 CanvasEnd: 428.88814432501835, 268.2016894748275
CanvasStart: 272.29306269353776, 268.2016894748275 CanvasEnd: 341.6523395564147, 268.2016894748275
CanvasStart: 341.6523395564147, 266.5502781209483 CanvasEnd: 272.29306269353776, 266.5502781209483
I tried binding the collection to my ItemsControl but it isn't working.

How do I bind dynamic byte array to button background in WPF

I'm trying to create a custom control that has a specific set of values on it. Also it has some custom events. I've been trying to get my byte array (blob) that I get from the database into my Custom Control background (button).
<Button Width="{Binding x}" Height="{Binding y}" VerticalAlignment="Center" Padding="100" Margin="1" FontSize="100" >
<Image Source="{Binding image_link}" HorizontalAlignment="Right" VerticalAlignment="Bottom" Height="{Binding x}" Width="{Binding y}" />
</Button>
And my custom control:
[JsonProperty("consoleButton.id")]
public int id { get; set; }
[JsonProperty("consoleButton.parent_id")]
public int? parent_id { get; set; }
[JsonProperty("consoleButton.console_id")]
public int console_id { get; set; }
[JsonProperty("consoleButton.image_link")]
public byte[] image_link { get; set; }
public string website_link { get; set; }
[JsonProperty("consoleButton.tag_name")]
public string tag_name { get; set; }
[JsonProperty("consoleButton.button_type")]
public Miscellaneous.Enums.buttontype button_type { get; set; }
public int x { get; set; }
public int y { get; set; }
//public string name { get; set; }
public BitmapImage brush { get; set; }
public ImageButton(int id, int? parent_id, int console_id, byte[] image_link, string website_link, string tag_name, Miscellaneous.Enums.buttontype button_type, int x, int y, double convertSize)
{
InitializeComponent();
this.id = id;
//this.name = "btn" + id.ToString();
this.parent_id = parent_id;
this.console_id = console_id;
this.image_link = image_link;
this.website_link = website_link;
this.tag_name = tag_name;
this.button_type = button_type;
this.x = Convert.ToInt32(x * convertSize);
this.y = Convert.ToInt32(y * convertSize);
BitmapImage btm;
using (MemoryStream ms = new MemoryStream(image_link))
{
btm = new BitmapImage();
btm.BeginInit();
btm.StreamSource = ms;
// Below code for caching is crucial.
btm.CacheOption = BitmapCacheOption.OnLoad;
btm.EndInit();
btm.Freeze();
}
brush = btm;
}
I've tried a few thing as you can see (added extra code) but nothing seems to work.
When you bind a UserControl's UI elements to own properties of the UserControl, you'll have to set the control instance as source object of the Binding.
One way to do that is to set the RelativeSource property:
<Image Source="{Binding image_link,
RelativeSource={RelativeSource AncestorType=UserControl}}" .../>
Note that you do not need an extra ImageSource (or BitmapImage) property like your brush property. WPF provides built-in type conversion, which will automatically convert byte[] to ImageSource.
Since your control's properties do not fire a change notification, it may also be necessary to set their value before calling InitializeComponent:
public ImageButton(...)
{
...
this.image_link = image_link;
...
InitializeComponent();
}
You are binding to the wrong property from what i can tell: Source="{binding image_link} when I believe it should be Source="{binding brush}
(Tiny restructuring of code btw):
BitmapImage btm = new BitmapImage();
using (MemoryStream ms = new MemoryStream(image_link))
{
ms.Position = 0;
btm.BeginInit();
btm.StreamSource = ms;
btm.CacheOption = BitmapCacheOption.OnLoad;
btm.EndInit();
}
btm.Freeze();
brush = btm;

XAML UWP progressring not acive when added to exisitng stackpanel

I try to create a progressring in the existing stackpanel as follows
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Height="150" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate >
<DataTemplate>
<StackPanel Orientation="Vertical" Height="150" Width="192" >
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}" Height="108" Width="192" HorizontalAlignment="Center" VerticalAlignment="Center" />
</StackPanel>
<StackPanel Orientation="Vertical">
<ProgressRing IsActive="{Binding IsActive}" Height="15" Width="15" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Vertical" >
<TextBlock Text="{Binding Name}" TextAlignment="Center" Height="22" Width="192" Foreground="White" FontFamily="Assets/GothamLight.ttf#GothamLight"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have a observable collection of items as follows
public class MyListItem
{
public int index { get; set; }
public BitmapImage Image { get; set; }
public string Name { get; set; }
public int Duration { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
}
private ObservableCollection<MyListItem> _yourCollection = new ObservableCollection<MyListItem>();
public ObservableCollection<MyListItem> YourCollection
{
get { return _yourCollection; }
set { _yourCollection = value; }
}
I populate Yourcollection items as follows
for (int i = 0; i < noofimage; i++)
{
YourCollection.Add(new MyListItem { index = i, Duration = JSONscript.playlist.Contents[i].Duration, Name = JSONscript.playlist.Contents[i].Name, Description = JSONscript.playlist.Contents[i].Description, Image = new BitmapImage(new Uri(Imagepath[i], UriKind.Absolute)), IsActive = false });
}
I try to set the IsActive of progressring as follows, but it doesnt work
if (YourCollection[videoindex].IsActive)
YourCollection[videoindex].IsActive = false;
else
YourCollection[0].IsActive = true;
Add INotifyPropertyChanged to your model class MyListItem
The UI will get an event when you add or remove items from the collection but not if a single object itself changes.. therefore use the interface above

Binding Data to UWP WinRT_XamlToolKit_Chart

I am trying to load a chart from a DataTemplate in a UWP program using the WinRT_XamlToolkit_Chart library. I am Binding the Chart Title and Data using the {Binding Property} syntax, but it is not loading the property as a data member of the chart object. I have included both my XAML
<Charting:Chart HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="600"
Height="400"
DataContext="{Binding}"
Title="{Binding Title}">
<Charting:LineSeries Title="{Binding Title}"
Margin="0"
IndependentValuePath="Name"
DependentValuePath="Amount"
IsSelectionEnabled="True"
ItemsSource="{Binding Data}"
/>
</Charting:Chart>
c# Object
public class DataChartNode : ExperimentNode
{
public DataChartNode(String title, String type)
{
Type = type;
Title = title;
Category = "Data Analysis";
}
public DataChartNode(String type)
{
Type = type;
Category = "Data Analysis";
Name = type;
Title = "Hello";
Length = 0;
Data = new List<DataPoint>();
}
public DataChartNode() { }
public string Type { set; get; }
public string Title { set; get; }
public int Length { set; get; }
public List<DataPoint> Data { set; get; }
}
Since you didn't provide the DataPoint class and didn't show how you build the data source that you bind to the Chart control, I wrote a simple demo that created the data source code behind which can work well with your XAML code snippet you can reference.
XAML Code (Same with you)
<Charting:Chart
Title="{Binding Title}"
Width="600"
Height="400"
HorizontalAlignment="Center"
VerticalAlignment="Center"
DataContext="{Binding}">
<Charting:LineSeries
Title="Title"
Margin="0"
DependentValuePath="Amount"
IndependentValuePath="Name"
IsSelectionEnabled="True"
ItemsSource="{Binding Data}" />
</Charting:Chart>
Code behind
public sealed partial class MainPage : Page
{
private Random _random = new Random();
public MainPage()
{
this.InitializeComponent();
var data = new List<DataPoint>();
for (int i = 0; i < 3; i++)
{
data.Add(new DataPoint { Name = "Name" + i, Amount = _random.Next(10, 100) });
}
DataChartNode charnode = new DataChartNode()
{
Title = "Hello",
Data = data
};
this.DataContext = charnode;
}
}
public class DataChartNode
{
public string Type { set; get; }
public string Title { set; get; }
public int Length { set; get; }
public List<DataPoint> Data { set; get; }
}
public class DataPoint
{
public string Name { get; set; }
public int Amount { get; set; }
}
Please check your code behind to find if anything wrong with it.

Binding not working on a single property of an object (while other properties are working)

I'm making a configurable WPF input dialog with the following code:
InputMessageBox.xaml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:local="clr-namespace:MediaManager.Forms" x:Class="MediaManager.Forms.InputMessageBox"
Title="{Binding Title}" Height="{Binding Height}" Width="{Binding Width}">
<Window.Background>
<SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ControlColorKey}}" />
</Window.Background>
<Grid>
<xctk:WatermarkTextBox Watermark="{Binding Message}" Margin="10" VerticalAlignment="Top" TabIndex="0" />
<Button Content="{Binding CancelButtonText}" Width="{Binding ButtonWidth}" Margin="10" HorizontalAlignment="Right" VerticalAlignment="Bottom" IsCancel="True" TabIndex="2" />
<Button Content="{Binding OkButtonText}" Width="{Binding ButtonWidth}" Margin="{Binding MarginOkButton}" HorizontalAlignment="Right" VerticalAlignment="Bottom" IsDefault="True" TabIndex="1" />
</Grid>
InputMessageBox.xaml.cs
public partial class InputMessageBox : Window
{
public InputMessageBox(inputType inputType)
{
InitializeComponent();
switch (inputType)
{
case inputType.AdicionarConteudo:
{
Properties = new InputMessageBoxProperties()
{
ButtonWidth = 75,
CancelButtonText = "Cancelar",
Height = 108,
Message = "Digite o nome do conteudo a ser pesquisado...",
OkButtonText = "Pesquisar",
Title = string.Format("Pesquisar - {0}", Settings.Default.AppName),
Width = 430
};
break;
}
default:
break;
}
DataContext = Properties;
}
public InputMessageBoxProperties Properties { get; set; }
}
InputMessageBoxProperties.cs
public class InputMessageBoxProperties
{
public int ButtonWidth { get; set; }
public string CancelButtonText { get; set; }
public int Height { get; set; }
public string InputText { get; set; }
public Thickness MarginOkButton { get { return new Thickness(10, 10, ButtonWidth + 15, 10); } }
public string Message { get; set; }
public string OkButtonText { get; set; }
public string Title { get; set; }
public int Width { get; set; }
}
When i call it, every binding is working as expected but one, the Width property. When i debug, the width property value is 430, but the width of the frame itself is a lot bigger than that. The confusing part is that the rest of the binding are working. Why is it happening?
You can fix that by setting the Width's Binding Mode to TwoWay :
Width="{Binding Width,Mode=TwoWay}"
You might as well consider implementing the INotifyPropertyChanged interface, so that the UI will automatically be notified in case any changes occurs in those properties :
public class InputMessageBoxProperties:INotifyPropertyChanged
{
private int _width ;
public int Width
{
get
{
return _width;
}
set
{
if (_width == value)
{
return;
}
_width = value;
OnPropertyChanged();
}
}
// add the other properties following the same pattern
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}

Categories

Resources