Trying to bind the height/width of a grid to a ViewModel - c#

So I have a grid inside a viewbox. Now the grid scales with the viewbox fine. However, I need to know the height and width of the grid in my ViewModel. However it doesn't seem to ever set the Height to anything?
<Viewbox VerticalAlignment="Top" Margin="5,20,5,0">
<Border BorderThickness="6" BorderBrush="Black" CornerRadius="2">
<Grid MinHeight="300" MinWidth="400" Height="{Binding Height, Mode=TwoWay}" Width="{Binding Width, Mode=TwoWay}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="0" >
<ItemsControl ItemsSource="{Binding Viewers}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</Border>
</Viewbox>
And in my ViewModel:
private int _height;
public int Height
{
get
{
return _height;
}
set
{
_height = value;
OnPropertyChanged("Height");
}
}
private int _width;
public int Width
{
get
{
return _width;
}
set
{
_width = value;
OnPropertyChanged("Width");
}
}
Any ideas?

You would need a OneWayToSource binding on ActualWidth/Height as those properties are readonly, so far i have seen any good solutions to that as WPF reject bindings outright if set on a readonly dependency property.

Related

How do I get the width of an element generated by Binding?

I generate a list of elements with an ObservableList and an ItemsControl.
But I need to know the ActualWidth of the single controls with reference to the bound item in the list.
I need the Width to cut down my Polyline so that it is no longer than the Canvas
I tried a OneWayToSource and a TwoWay Binding back to the Item in the List with the Width Property. Always got NaN or 0.
<ItemsControl x:Name="GraphLB" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Padding="5, 0, 5, 0">
<StackPanel>
<TextBlock Text="{Binding Text}" Foreground="{Binding Foreground}"/>
<Border BorderBrush="{Binding Foreground}" BorderThickness="0.5">
<Canvas MinHeight="40" MinWidth="100"
Height="{Binding Height, Mode = TwoWay }"
Width="{Binding Width, Mode = TwoWay }">
<Polyline Points="{Binding Line}" Stroke="{Binding Foreground}" StrokeThickness="1"/>
</Canvas>
</Border>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And the Class for the Bound Objects
class GraphListObject : LabelListObject
{
public double Height { get; set; }
public double Width { get; set; }
private getProperty<PointCollection> getLine;
public PointCollection Line { get { return GetLine(); } }
internal getProperty<PointCollection> GetLine { get => getLine; set { getLine = value; } }
}
I expected that the Width="{Binding Width, Mode = TwoWay }" would set the Value in the Element of the List to the Width of the Canvas Element, but the actual output is 0.
Sadly the Polyline is drawn outside the borders:

Add rectangle to Canvas using MVVM [duplicate]

This question already has answers here:
Add n rectangles to canvas with MVVM in WPF
(2 answers)
Closed 5 years ago.
How do i add Rectangle to my View using MVVM?
This is the code for my view.
<Grid>
<Image x:Name="img" Source="{Binding ImagePath, Source={x:Static vm:DrawingVM.instance}, Converter={StaticResource nullImageConverter}}" Stretch="None" >
</Image>
<ItemsControl ItemsSource="{Binding ListRectangle, Source={x:Static vm:DrawingVM.instance}}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent" x:Name="cnvas" Width="{Binding ElementName=img, Path=ActualWidth}"
Height="{Binding ElementName=img,Path=ActualHeight}"
LayoutTransform="{Binding ElementName=img, Path=LayoutTransform}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<!--<command:EventToCommand CommandParameter="{Binding ElementName=cnvas}" Command="{Binding MouseDownCommand, Source={x:Static vm:DrawingVM.instance}}" PassEventArgsToCommand="True" />-->
<ei:CallMethodAction MethodName="MouseDownEvente" TargetObject="{Binding Source={x:Static vm:DrawingVM.instance}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</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}" Stroke="Blue" Fill="Transparent" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
and this is my View Model
Canvas canvas = new Canvas();
public void MouseDownEvente(object s, MouseButtonEventArgs e)
{
try
{
if (s == null) return;
canvas = s as Canvas;
if (canvas == null) return;
startPoint = e.GetPosition(canvas);
// Remove the drawn rectanglke if any.
// At a time only one rectangle should be there
//if (rectSelectArea != null)
// canvas.Children.Remove(rectSelectArea);
// Initialize the rectangle.
// Set border color and width
rectSelectArea = new Rectangle
{
Stroke = Brushes.Blue,
StrokeThickness = 2,
Fill = Brushes.Transparent,
};
Canvas.SetLeft(rectSelectArea, startPoint.X);
Canvas.SetTop(rectSelectArea, startPoint.X);
canvas.Children.Add(rectSelectArea);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
throw ex;
}
}
But its throwing an error:
Cannot explicitly modify Children collection of Panel used as ItemsPanel for ItemsControl. ItemsControl generates child elements for Panel.
So how do i solve this?
I tried searching the same problem with mine. And used the solution that worked to them. But the error still persist. Can someone help me. Thank you.
Cannot explicitly modify Children collection of Panel used as ItemsPanel for ItemsControl. ItemsControl generates child elements for Panel.
This means that you cannot use Canvas.Children.Add when you use Canvas as an ItemsPanel for an ItemsControl. You should add items on where the ItemsControl.ItemsSource property is bound to (in your case ListRectangle).

Binding collection of many types to RadTileView with specified Tile spans

I am trying to create view using Telerik for WPF. The View should present collection loaded in VM after button click. Every element from collection is inheriting from base class:
public class BindableProfileData : BindableBase
{
private ProfileItem _profileModel;
private BindableDPDataItem _dataModel;
public int Row
{
get { return _profileModel.DrawingSettings.Row; }
}
public int Column
{
get
{
return _profileModel.DrawingSettings.Column;
}
}
public int RowSpan
{
get { return _profileModel.DrawingSettings.RowSpan; }
}
public int ColumnSpan
{
get { return _profileModel.DrawingSettings.ColumnSpan; }
}
}
so for example in collection may appear object of
BarContainer : BindableProfileData {}
Now to present content i created that view:
<Grid>
<FrameworkElement.Resources>
<DataTemplate x:Key="ItemTemplate" >
<TextBlock Text="{Binding Name}" />
</DataTemplate>
<DataTemplate x:Key="ContentTemplate">
<StackPanel>
<TextBlock Text="{Binding Row, StringFormat='Row: {0}'}"/>
<TextBlock Text="{Binding Column, StringFormat='Column: {0}'}"/>
<TextBlock Text="{Binding RowSpan, StringFormat='RowSpan: {0}'}"/>
<TextBlock Text="{Binding ColumnSpan, StringFormat='ColumnSpan: {0}'}"/>
</StackPanel>
</DataTemplate>
</FrameworkElement.Resources>
<Grid>
<telerik:RadTileView x:Name="xTileView"
ItemTemplate="{StaticResource ItemTemplate}"
ContentTemplate="{StaticResource ContentTemplate}"
ItemsSource="{Binding SelectedCategory}"
IsAutoScrollingEnabled="True"
ColumnWidth="500"
RowHeight="300">
<telerik:RadTileView.ItemsPanel>
<ItemsPanelTemplate>
<controlls:MultipleRowsAndColumnsPanel RowsCount="4" ColumnsCount="3"/>
</ItemsPanelTemplate>
</telerik:RadTileView.ItemsPanel>
<telerik:RadTileView.ItemContainerStyle>
<Style TargetType="telerik:RadTileViewItem">
<Setter Property="controlls:TileViewAttachedProperties.Row" Value="{Binding Row}"/>
<Setter Property="controlls:TileViewAttachedProperties.Column" Value="{Binding Column}"/>
<Setter Property="controlls:TileViewAttachedProperties.RowSpan" Value="{Binding RowSpan}"/>
<Setter Property="controlls:TileViewAttachedProperties.ColumnSpan" Value="{Binding ColumnSpan}"/>
</Style>
</telerik:RadTileView.ItemContainerStyle>
</telerik:RadTileView>
</Grid>
</Grid>
Which uses helpers from telerik guides (MultipleRowsAndColumnsPanel, TileViewAttachedProperties)
I have two problems. First why view not appears after Collection change.
When I cut:`
<telerik:RadTileView.ItemsPanel>
<ItemsPanelTemplate>
<controlls:MultipleRowsAndColumnsPanel RowsCount="4" ColumnsCount="3"/>
</ItemsPanelTemplate>
</telerik:RadTileView.ItemsPanel>
`
from code and paste it again (Visual Studio edit and run) view appears just How I want it to be. So it looks like ItemsPanel not reacting on collection Change, but how to fix it?
And the second question how can I specify different templates for different classes in collection. I found similar post but I don't know how to do it with RadViewTableItem because it has two elements (ItemTemplate and ContentTemplate) usually specified in RadTileView.
Any advice whould be very helpfull for me. Thank you :)

Itemscontrol display only first element of the collection

I have ItemControl
<ItemsControl ItemsSource="{Binding CanvasCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="{Binding CanvasSize}" Height="{Binding CanvasSize}" Background="RoyalBlue" />
</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 DataType="{x:Type viewModels:VertexViewModel}">
<Thumb Width="11" Height="11">
<Thumb.Template>
<ControlTemplate>
<Ellipse Width="11" Height="11" Fill="{Binding Fill}" />
</ControlTemplate>
</Thumb.Template>
</Thumb>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And composite collection with currently one ObservableCollection:
private ObservableCollection<VertexViewModel> Points { get; set; }
public CompositeCollection CanvasCollection { get; set; }
While debugging, I see that collection contains two elements, as I accepted, but only first is displaying on Canvas. When I call Refresh() method in model, I see that binding working, but only for first element.
Adding Point to CompositeCollection:
Points = new ObservableCollection<VertexViewModel>();
CanvasCollection = new CompositeCollection()
{
Points
};
I had to use container for collectoin:
CanvasCollection = new CompositeCollection
{
new CollectionContainer() {Collection = Points},
Polygon,
};

A Simple Photo Album with Pinch and Zoom using FlipView

I'm trying to create a simple photo album (Windows Store App) using Flip View.
I have the Image element embedded within a ScrollViewer. I'm able to browse through the photos, but I'm looking to do the following things.
The image should fill the height of the screen uniformly [when the image is not zoomed]. I get vertical scrollbars for few items. I dont have this problem when the height of all the images are the same.
When I change the orientation of the screen, a part of the image is clipped on the right side.
The scrollviewer should forget the zoom level (reset zoom factor to 1) when I move between pages.
This is the code I have right now. What Am I doing wrong? And what should I add in my EventHandler to reset my ScrollViewer's zoom factor.
<FlipView
Name="MainFlipView"
Margin="0"
Height="{Binding ActualHeight, ElementName=pageRoot, Mode=OneWay}"
Width="{Binding ActualWidth, ElementName=pageRoot, Mode=OneWay}"
Background="Black">
<FlipView.ItemTemplate>
<DataTemplate>
<ScrollViewer Name="myScrollViewer" ZoomMode="Enabled"
Height="{Binding ActualHeight, ElementName=pageRoot, Mode=OneWay}"
Width="{Binding ActualWidth, ElementName=pageRoot, Mode=OneWay}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
MinZoomFactor="0.5"
MaxZoomFactor="2.5"
Margin="0" >
<Image Source="{Binding Path=Image}"
Name="MainImage" Stretch="Uniform" />
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
What user2199147 said should solve your first bullet point, the other two I had to fix programmatically, though it should be noted that I also had to use the VisualTreeHelper class which you'll have to import, and an extension method to help me use the helper class.
First of all, I had to a method from the VisualTreeHelper extension, which finds the first element in the FlipView that is of any type:
private T FindFirstElementInVisualTree<T>(DependencyObject parentElement) where T : DependencyObject
{
if (parentElement != null)
{
var count = VisualTreeHelper.GetChildrenCount(parentElement);
if (count == 0)
return null;
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parentElement, i);
if (child != null && child is T)
return (T)child;
else
{
var result = FindFirstElementInVisualTree<T>(child);
if (result != null)
{
return result;
}
}
}
}
return null;
}
For going into portrait mode, I added a callback handler for WindowSizeChanged, and simply reset all the ScrollViewers in the flip view back to their default
private void WindowSizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
//Reset scroll view size
int count = MainFlipView.Items.Count;
for(int i = 0; i < count; i++)
{
var flipViewItem = MainFlipView.ItemContainerGenerator.ContainerFromIndex((i));
var scrollViewItem = FindFirstElementInVisualTree<ScrollViewer>(flipViewItem);
if (scrollViewItem is ScrollViewer)
{
ScrollViewer scroll = (ScrollViewer)scrollViewItem;
scroll.Height = e.Size.Height; //Reset width and height to match the new size
scroll.Width = e.Size.Width;
scroll.ZoomToFactor(1.0f);//Zoom to default factor
}
}
}
And then in your constructor you need Window.Current.SizeChanged += WindowSizeChanged; in order for the callback to ever be called.
Now, for setting each ScrollViewer back to their default positions, we do a similar process, only whenever the FlipView selection is changed, we reset the ScrollViewer back to its default zoom factor
private void FlipViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is FlipView)
{
FlipView item = (FlipView)sender;
var flipViewItem = ((FlipView)sender).ItemContainerGenerator.ContainerFromIndex(((FlipView)sender).SelectedIndex);
var scrollViewItem = FindFirstElementInVisualTree<ScrollViewer>(flipViewItem);
if (scrollViewItem is ScrollViewer)
{
ScrollViewer scroll = (ScrollViewer)scrollViewItem;
scroll.ScrollToHorizontalOffset(0);
scroll.ScrollToVerticalOffset(0);
scroll.ZoomToFactor(1.0f);
}
}
}
And again, we have to have a call in the constructor that looks like MainFlipView.SelectionChanged += FlipViewSelectionChanged;
I know these methods seem really hackish and roundabout, because they are, but it's what worked for me, and I hope this helps.
try changing the height and width bindings from the scrollviewer to the image.
<FlipView
Name="MainFlipView"
Margin="0"
Height="{Binding ActualHeight, ElementName=pageRoot, Mode=OneWay}"
Width="{Binding ActualWidth, ElementName=pageRoot, Mode=OneWay}"
Background="Black">
<FlipView.ItemTemplate>
<DataTemplate>
<ScrollViewer Name="myScrollViewer" ZoomMode="Enabled"
HorizontalAlignment="Center"
VerticalAlignment="Center"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
MinZoomFactor="0.5"
MaxZoomFactor="2.5"
Margin="0" >
<Image Source="{Binding Path=Image}"
Height="{Binding ActualHeight, ElementName=pageRoot, Mode=OneWay}"
Width="{Binding ActualWidth, ElementName=pageRoot, Mode=OneWay}"
Name="MainImage" Stretch="Uniform" />
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
Good practice for WinRT is:
1) Make Attached Property for ScrollViewer, which will change ZoomFactor
public class ScrollViewerExtension : DependencyObject
{
public static readonly DependencyProperty ScrollViewerZoomFactorProperty = DependencyProperty.RegisterAttached(
"ScrollViewerZoomFactor", typeof(double), typeof(ScrollViewerExtension), new PropertyMetadata(default(double), OnZoomFactorChanged));
public static void SetScrollViewerZoomFactor(DependencyObject element, double value)
{
element.SetValue(ScrollViewerZoomFactorProperty, value);
}
public static double GetScrollViewerZoomFactor(DependencyObject element)
{
return (double)element.GetValue(ScrollViewerZoomFactorProperty);
}
private static void OnZoomFactorChanged(DependencyObject depObject, DependencyPropertyChangedEventArgs args)
{
if (depObject is ScrollViewer)
{
var scrollViewer = (ScrollViewer)depObject;
var zoomValue = (double)args.NewValue;
if (!Double.IsNaN(zoomValue))
scrollViewer.ZoomToFactor((float)zoomValue);
}
else
{
throw new Exception("ARE YOU KIDDING ME ? ITS NOT SCROLLVIEWER");
}
}
}
2) Changer FlipViewItem Template
<Style TargetType="FlipViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Width="1040">
<ScrollViewer HorizontalAlignment="Stretch"
HorizontalScrollBarVisibility="Auto"
MaxZoomFactor="4"
MinZoomFactor="1"
Tag="{Binding IsSelected}"
VerticalScrollBarVisibility="Auto"
VerticalScrollMode="Auto"
ZoomMode="Enabled"
extension:ScrollViewerExtension.ScrollViewerZoomFactor="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected, Converter={StaticResource IsSelectedToZoom}}">
<ContentPresenter />
</ScrollViewer>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
3) Create converter, which will change ScrollViewerZoomFactor to default value if item isnt selected.
public class IsSelectedToZoomConverter :DependencyObject, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var val = (bool) value;
return val ? Double.NaN : 1.0;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
4) FlipView code will look like this:
<FlipView x:Name="FlipView"
Grid.Row="5"
Width="1040"
MinHeight="392"
MaxHeight="600"
ItemsSource="{Binding Path=CurrentSession.Photos}"
Visibility="{Binding CurrentSession.HasContent,
Converter={StaticResource BoolToVisibility}}">
<FlipView.ItemContainerStyle>
<Style TargetType="FlipViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Width="1040">
<ScrollViewer HorizontalAlignment="Stretch"
HorizontalScrollBarVisibility="Auto"
MaxZoomFactor="4"
MinZoomFactor="1"
Tag="{Binding IsSelected}"
VerticalScrollBarVisibility="Auto"
VerticalScrollMode="Auto"
ZoomMode="Enabled"
extension:ScrollViewerExtension.ScrollViewerZoomFactor="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=IsSelected,
Converter={StaticResource IsSelectedToZoom}}">
<ContentPresenter />
</ScrollViewer>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</FlipView.ItemContainerStyle>
<FlipView.ItemTemplate>
<DataTemplate>
<Image HorizontalAlignment="Stretch" Source="{Binding Path=Path}" />
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
why do you have a ScrollViewer within you FlipViewItemTemplate?
Thie template will be used for each Item, so for each Image you add to your ItemList.
that said it shoud be enough to have the Image element within your Template.
this should at least avoid the scrollbars for images that are bigger than your screen, cause then the Stretch="Uniform" should handle the resizing...

Categories