I want to add a set of rectangles to the main window of my mvvm application. In my viewModel I've got a collection of objects which I convert to System.Windows.Shapes.Rectangle classes with a converter (code below):
ViewModel:
RecognizedValueViewModel
{
public ObservableCollection<BarcodeElement> BarcodeElements
{
get { return _BarcodeElements; }
set { _BarcodeElements = value; }
}
public RecognizedValueViewModel()
{
BarcodeElements = InitializeBarcodeElements();
}
}
Converter:
public BarcodeElementToRectangleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Rectangle barcodeRectangle = GetRectangleFromBarcodeElement(value as BarcodeElement);
return barcodeRectangle;
}
}
The rectangles should be shown in a canvas in my MainWindow:
<Canvas x:Name="Canvas_Image_Main">
<!-- Show rectangles here -->
</Canvas>
I would add Rectangles to canvas in code but I don't now how many rectangles are there at runtime. Is there a way how I can achieve this? Tank you.
In a proper MVVM approach you would have a view model with an abstract representation of a list of rectangles, e.g. like this:
public class RectItem
{
public double X { get; set; }
public double Y { get; set; }
public double Width { get; set; }
public double Height { get; set; }
}
public class ViewModel
{
public ObservableCollection<RectItem> RectItems { get; set; }
}
Then you would have a view that uses an ItemsControl to visualize a collection of such Rect items. The ItemsControl would have a Canvas as its ItemsPanel and an appropriate ItemContainerStyle and ItemTemplate which each bind to the appropriate view model properties. It might look like this:
<ItemsControl ItemsSource="{Binding RectItems}">
<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.ItemTemplate>
<DataTemplate>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="Black"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
An alternative without Bindings in Style Setters (which don't work in UWP) might look like this:
<ItemsControl ItemsSource="{Binding RectItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="Black">
<Rectangle.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can bind the collection of rectangles to an ItemControl and set its height, width and margin:
<ItemsControl ItemsSource="{Binding Path=RectangleCollection,Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate >
<Canvas>
<Rectangle Stroke="Black" Heigth={some converter} Width={some converter} Margin={Some Converter}>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemControl>
Just an idea to get you started...
This question already has answers here:
UWP Binding in Style Setter not working
(2 answers)
Closed 6 years ago.
My Code does not bind to the X and Y property of the items in the observable Collection. What is wrong:
<ItemsControl ItemsSource="{Binding LED}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="SkyBlue"/>
</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.ItemTemplate>
<DataTemplate>
<Ellipse Stroke="{Binding Color}" Fill="{Binding FillColor}" StrokeThickness="1" Width="40" Height="40"></Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It does bind to Color and FillColor.
Here is the Shape class, that is stored in the ObservableCollection LED:
class Shape
{
public int X { get; private set; }
public int Y { get; private set; }
public string Color { get; private set; }
public string FillColor { get; private set; }
public Shape (int x, int y, string color, string fillColor)
{
X = x;
Y = y;
Color = color;
FillColor = fillColor;
}
}
The documentation for the Setter.Value property has the following note:
Windows Presentation Foundation (WPF) and Microsoft Silverlight supported the ability to use a Binding expression to supply the Value for a Setter in a Style. The Windows Runtime doesn't support a Binding usage for Setter.Value (the Binding won't evaluate and the Setter has no effect, you won't get errors, but you won't get the desired result either). When you convert XAML styles from WPF or Silverlight XAML, replace any Binding expression usages with strings or objects that set values, or refactor the values as shared {StaticResource} markup extension values rather than Binding-obtained values.
As a workaround, you could try using a RenderTransform instead:
<Ellipse Stroke="{Binding Color}" Fill="{Binding FillColor}" StrokeThickness="1" Width="40" Height="40">
<Ellipse.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</Ellipse.RenderTransform>
</Ellipse>
I have a hard coded Path shape inside of a Canvas. I want to have multiple shapes inside this canvas so I'm trying to shove the information of each shape into a class and then use an ItemsControl to render each one. When I use the ItemsControl, each shape is in a incorrect location (Too far up and left).
Displays Correctly
<Canvas>
<Path Style="{StaticResource OverlayPath}"
Height="87.934"
Width="96.067"
Canvas.Left="348.456"
Canvas.Top="204.525"
Data="M432.9,245.5 L428.26666,258.46667 439.86716,261.26627 443.46698,246.46662 443.06664,242.33348 437.06651,242.60046 428.26633,239.13402 429.9994,232.33489 424.66584,230.73536 423.86545,234.46865 414.39771,236.46845 413.86433,239.66813 409.99697,236.73509 403.8631,235.80185 402.66265,233.66874 405.86266,231.13566 404.39584,224.73631 407.06279,221.93696 407.19614,217.00454 402.52898,211.00525 401.46255,204.73933 389.99435,207.00605 387.06071,211.4055 387.32706,222.20415 377.85934,219.93777 355.4564,218.33797 354.38926,226.20365 348.38853,227.80345 348.52187,233.93602 351.18886,239.53532 C351.18886,239.53532 356.12278,238.6021 355.72274,238.6021 355.32269,238.6021 361.99016,251.80045 361.99016,251.80045 L366.79074,253.53357 366.39069,258.5996 369.05768,259.13287 367.32414,268.73167 368.57429,275.93113 371.64132,279.19775 374.44166,279.73103 374.57501,286.46394 387.57658,287.19684 387.84328,290.06317 394.64409,291.66265 396.64434,285.79638 394.77744,284.99648 396.24429,279.99709 398.17785,279.13053 396.17761,276.99746 398.91128,274.99771 406.64554,277.86402 417.78022,267.79859 417.91357,262.53257 414.12144,259.02467 425.4228,249.8258 420.92226,244.72642 423.52258,243.02663 428.92323,242.82666 z" />
</Canvas>
Displays Incorrectly
<Canvas>
<ItemsControl ItemsSource={Binding CanvasPaths}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Style="{StaticResource OverlayPath}"
Data="{Binding Data}"
Height="{Binding Height}"
Width="{Binding Width}"
Canvas.Left="{Binding CanvasLeft}"
Canvas.Top="{Binding CanvasTop}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
Model
public class CanvasPath
{
public CanvasPath(string data, double height, double width, double canvasTop, double canvasLeft)
{
Data = data;
Height = height;
Width = width;
CanvasTop = canvasTop;
CanvasLeft = canvasLeft;
}
public string Data { get; set; }
public double Height { get; set; }
public double Width { get; set; }
public double CanvasTop { get; set; }
public double CanvasLeft { get; set; }
}
ViewModel
public class TestViewModel
{
private ObservableCollection<CanvasPath> test = new ObservableCollection<CanvasPath>()
{
new CanvasPath("M432.9,245.5 L428.26666,258.46667 439.86716,261.26627 443.46698,246.46662 443.06664,242.33348 437.06651,242.60046 428.26633,239.13402 429.9994,232.33489 424.66584,230.73536 423.86545,234.46865 414.39771,236.46845 413.86433,239.66813 409.99697,236.73509 403.8631,235.80185 402.66265,233.66874 405.86266,231.13566 404.39584,224.73631 407.06279,221.93696 407.19614,217.00454 402.52898,211.00525 401.46255,204.73933 389.99435,207.00605 387.06071,211.4055 387.32706,222.20415 377.85934,219.93777 355.4564,218.33797 354.38926,226.20365 348.38853,227.80345 348.52187,233.93602 351.18886,239.53532 C351.18886,239.53532 356.12278,238.6021 355.72274,238.6021 355.32269,238.6021 361.99016,251.80045 361.99016,251.80045 L366.79074,253.53357 366.39069,258.5996 369.05768,259.13287 367.32414,268.73167 368.57429,275.93113 371.64132,279.19775 374.44166,279.73103 374.57501,286.46394 387.57658,287.19684 387.84328,290.06317 394.64409,291.66265 396.64434,285.79638 394.77744,284.99648 396.24429,279.99709 398.17785,279.13053 396.17761,276.99746 398.91128,274.99771 406.64554,277.86402 417.78022,267.79859 417.91357,262.53257 414.12144,259.02467 425.4228,249.8258 420.92226,244.72642 423.52258,243.02663 428.92323,242.82666 z",
87.934, 96.067, 204.525, 348.456)
};
public ObservableCollection<CanvasPath> CanvasPaths
{
get
{
return test;
}
}
}
The Canvas.Left and Canvas.Top bindings in your ItemTemplate have no effect, because the Path control in the DataTemplate does not have a Canvas parent.
In order to make it work you would have to set the ItemsPanel and ItemContainerStyle properties like this:
<ItemsControl ItemsSource="{Binding CanvasPaths}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding CanvasLeft}"/>
<Setter Property="Canvas.Top" Value="{Binding CanvasTop}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Style="{StaticResource OverlayPath}"
Data="{Binding Data}"
Height="{Binding Height}"
Width="{Binding Width}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have been trying to figure out how to bind an ObservableCollection<FrameworkElements> to an ItemsControl. I have an existing project which relies heavily on code behind and canvas's without binding which I am trying to update to use mvvm and prism.
The ObservableCollection is going to be populated with a number of Path items. They are generated from an extermal library which I use. The library functions correctly when I manually manipulate the canvas itself.
Here is a snippet of the code from the ViewModel:
ObservableCollection<FrameworkElement> _items;
ObservableCollection<FrameworkElement> Items
{
get { return _items; }
set
{
_items = value;
this.NotifyPropertyChanged("Items");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Supporting XAML
<ItemsControl ItemsSource="{Binding Items}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="canvas" IsItemsHost="True">
<Canvas.Background>
<SolidColorBrush Color="White" Opacity="100"/>
</Canvas.Background>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The issue I am experiencing is that the Path's never draw. Any suggestion on where I am going wrong and where to start the debug process?
Your view model should contain an abstract representation of a Path, e.g.
public class PathData
{
public Geometry Geometry { get; set; }
public Brush Fill { get; set; }
public Brush Stroke { get; set; }
public double StrokeThickness { get; set; }
// ... probably more Stroke-related properties
}
If you now have a collection of PathData objects like
public ObservableCollection<PathData> Paths { get; set; }
your ItemsControl could have an ItemsTemplate as shown below:
<ItemsControl ItemsSource="{Binding Paths}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Data="{Binding Geometry}" Fill="{Binding Fill}"
Stroke="{Binding Stroke}" StrokeThickness="{Binding StrokeThickness}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You would now add PathData instance like this:
Paths.Add(new PathData
{
Geometry = new RectangleGeometry(new Rect(100, 100, 100, 100)),
Fill = Brushes.AliceBlue,
Stroke = Brushes.Red,
StrokeThickness = 2
});
ObservableCollection<FrameworkElement> Yeah, you can stop there. That isn't MVVM. Also, you define the item template as a (blank, I guess you could say) path object. You do NOT bind that to the properties of the Paths you throw in your observable collection.
A better implementation would be to have an ObservableCollection<string> containing path data, AKA the stuff that actually defines the path's shape, then bind this to your path.
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="canvas" IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Data="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you had used Snoop to examine your UI at runtime, you'd have seen that you had lots of Path objects out there, but that they all had empty data.
Also ALSO, if you're using a canvas for the Path shapes, you need to set Canvas.Left and Canvas.Top attached properties on your Paths. A true MVVM hero would define a model thusly
public PathData{
public double Left {get;set;}
public double Top {get;set;}
public string Data {get;set;}
}
then expose these in your ViewModel
public ObservableCollection<PathData> Items {get;private set;}
then bind your Path to them
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Canvas.Left="{Binding Left}"
Canvas.Top="{Binding Top}"
Data="{Binding Data}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
I have many different windows and they all have a different design. The different windows are selected in a menu. The menu has screenshots of the windows in every row. I would like to find a way and automatize the following steps:
make a screenshot of the window
insert the picture in the new window
link the click handler
So, actually my question is, whether it is possible to get the image of the yet hidden window during runtime
This should give the idea. I have a control template for my windows. This template has a VisualTarget element that wraps all the other controls in each instance. So the code below works for me.
class ThumbnailView
{
public Guid WindowGuid { get; set; }
public Window ApplicationWindowInstance { get; set; }
public Border ThumbnailVisual
{
get {
return (this.ApplicationWindowInstance.
Template.FindName("VisualTarget",
this.ApplicationWindowInstance) as Border);
}
}
}
<Border BorderThickness="0,0,0,0" Cursor="Hand">
<Border.Background>
<VisualBrush Visual="{Binding ThumbnailVisual}"/>
</Border.Background>
</Border>
Edit: Here is something more general
ObservableCollection<WindowInstance> _windows = new ObservableCollection<WindowInstance>();
class WindowInstance
{
public Window CurrentWindowInstance { get; set; }
public DependencyObject CurrentVisual {
get {
return VisualTreeHelper.GetChild(CurrentWindowInstance, 0);
}
}
}
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="0,0,0,0" Width="50" Height="50">
<Border.Background>
<VisualBrush Visual="{Binding CurrentVisual}"/>
</Border.Background>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit: Examples above are using live visual brushes which could lead to a performance breakdown. So following is the answer for frozen window thumbnails.
ObservableCollection<BitmapFrame> _windowCaptures = new ObservableCollection<BitmapFrame>();
TestWindow testWindow = new TestWindow();
RenderTargetBitmap bitmap = new RenderTargetBitmap((int)testWindow.Width, (int)testWindow.Height, 96, 96,
PixelFormats.Pbgra32);
bitmap.Render((Visual)VisualTreeHelper.GetChild(testWindow, 0));
_windowCaptures.Add(BitmapFrame.Create(bitmap));
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Height="100" Width="100" Source="{Binding}"></Image>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>