Dynamic windows preview generation with WPF - c#

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>

Related

WPF Canvas elements all have a different perception of what Y means [duplicate]

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...

WPF: cannot set margin properly [duplicate]

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...

Add a TextBox as the last element of an ItemsControl with WrapPanel

I am trying to implement the following scanarios
APPROACH SO FAR
Tried to implement it with an ItemsControl (with WrapPanel) and a TextBox wrapped inside a WrapPanel, but it does not have a desired output as there are two WrapPanels wrapping separately
<toolkit:WrapPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding someThing}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Border>
<TextBlock Text="somesomething" />
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBox/>
</toolkit:WrapPanel>
I am thinking if I can add the TextBox at the END of the ItemsControl, but failed to do so. Please specify if there is any other workaround/ solution to any of my approaches
You need to use DataTemplateSelector for the ItemsControl and specify different templates for different list items.
public class BlockItem
{
// TODO
}
public class BoxItem
{
// TODO
}
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate BlockTemplate { get; set; }
public DataTemplate BoxTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
if (item is BlockItem) return BlockTemplate;
else if (item is BoxItem) return BoxTemplate;
return base.SelectTemplateCore(item);
}
}
XAML:
<ItemsControl ItemsSource="{Binding someObject}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplateSelector>
<local:MyTemplateSelector>
<local:MyTemplateSelector.BlockTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="something"/>
</Grid>
</DataTemplate>
</local:MyTemplateSelector.BlockTemplate>
<local:MyTemplateSelector.BoxTemplate>
<DataTemplate>
<Grid>
<TextBox Text="something"/>
</Grid>
</DataTemplate>
</local:MyTemplateSelector.BoxTemplate>
</local:MyTemplateSelector>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
And you then add different types of objects to your items source:
someObject.Add(new BlockItem());
someObject.Add(new BlockItem());
someObject.Add(new BlockItem());
someObject.Add(new BlockItem());
someObject.Add(new BoxItem());
If you want the TextBox to be the last element, then you need it to be the last item in your ItemsSource list.

Binding Shapes.Path items to a 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>

Grouping with strongly type group key in WPF

Every example and I article I found, is about grouping items by one property and displaying it. But what I have, is a strongly type group key, which I want to display. Here is models and grouping logic:
The item interface
public interface IItem {
string Title { get; }
string ToolTip { get; }
object Icon { get; }
Type GroupType { get; }
}
IItem has many implementations like this:
public class Item : IItem {
public string Title { get; private set; }
public string ToolTip { get; private set; }
public object Icon { get; private set; }
// I have many implementation of IGroup which I will use them in GroupType properties.
public Type GroupType { get { return SomeGroupTypeHere; } }
}
And here is the group interface:
public interface IGroup {
string Name { get; }
object Icon { get; }
}
and it has many implementations too.
I collect them in my view model (by getting help from Autofac):
public class MyViewModel {
private readonly IEnumerable<IGrouping<IGroup, IItem>> _items;
public MyViewModel(IEnumerable<IGroup> groups, IEnumerable<IItem> items){
_items = items.GroupBy(t => {
var g = groups.First(u => u.GetType() == t.GroupType);
return g;
});
}
public IEnumerable<IGrouping<IGroup, IItem>> Items {
get { return _items; }
}
}
Now, the problem is, how to display this grouped items, in a ItemsControl?
<ItemsControl ItemsSource="{Binding Items}" Margin="20 20 20 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate >
<!-- here is my template that uses IItem properties, example: -->
<Button Content="{Binding Title}"
ToolTip="{Binding ToolTip}"/>
</DataTemplate >
</ItemsControl.ItemTemplate>
</ItemsControl>
The code, just only displays the first item in each group, and I have no idea what to do to show group headers (that use IGroup properties) and also show all items in each group. Any suggestion please? Any article or blog-post will be very useful. Thanks in advance.
You want to use HierarchicalDataTemplate when you want to display Grouped data, Change your ItemsControl.ItemTemplate to a `HierarchialDataTemplate'.
Sample (and Untested) HierarchialDataTemplate:
<ItemsControl ItemsSource="{Binding Items}" Margin="20 20 20 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding}">
<Button Content="{Binding Title}" ToolTip="{Binding ToolTip}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate >
<Button Content="{Binding Title}" ToolTip="{Binding ToolTip}"/>
</DataTemplate >
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Refer to this blogpost for step-by-step approach to HierarchialDataTemplates.
UPDATE
I have tried the above HierarchialDataTemplate with your code and it doesnt seem to work. However, if I replace ItemsControl with TreeView, it does work as expected.
So, I guess, you might want to have nested ItemControl for your case, Tested sample code as below:
<ItemsControl ItemsSource="{Binding Items}" Margin="20 20 20 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="{Binding Title}" ToolTip="{Binding ToolTip}"/>
<ItemsControl ItemsSource="{Binding}" Margin="15,0,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate >
<Button Content="{Binding Title}" ToolTip="{Binding ToolTip}"/>
</DataTemplate >
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the screen shot with this nested ItemsControls.
UPDATE 2
I think you need to update the template to display Group, the current template doesnt do that.
Updated Template is below:
<ItemsControl ItemsSource="{Binding Items}" Margin="20 20 20 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="{Binding Key.Name}" />
<ItemsControl ItemsSource="{Binding}" Margin="15,0,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate >
<Button Content="{Binding Title}" ToolTip="{Binding ToolTip}"/>
</DataTemplate >
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And the resulting Screenshot:

Categories