I'm trying to make a navigator, that shows a thumbnail of ScrollViewer Content. My application has work area, where I place objects and which I want to show in navigator:
<local:WorkArea x:Name="WorkArea" Grid.Row="1"/>
Work area is a UserControl, which has this structure:
<UserControl>
<ScrollViewer>
<Canvas/>
<ScrollViewer/>
<UserControl/>
My navigator is also a UserControl:
<local:Navigator DataContext="{Binding ElementName=WorkArea, Path=Content}"
HorizontalAlignment="Right" VerticalAlignment="Bottom"
Width="250" Height="250" Margin="0,0,10,10" Grid.Row="1"/>
It's structure:
<UserControl x:Class="DbCreator.Navigator"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DbCreator"
mc:Ignorable="d" Opacity="0.9"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<local:WorkAreaToNavigatorConverter x:Key="WorkAreaToNavigatorConverter"/>
</UserControl.Resources>
<Border BorderBrush="#757575" BorderThickness="1"
HorizontalAlignment="Right" VerticalAlignment="Bottom"
Width="{Binding ElementName=ViewBox, Path=ActualWidth}"
Height="{Binding ElementName=ViewBox, Path=ActualHeight}">
<Viewbox x:Name="ViewBox" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=ActualWidth}"
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=ActualHeight}">
<Grid Background="White">
<Rectangle Width="{Binding Content.ActualWidth}"
Height="{Binding Content.ActualHeight}"
Name="Thumbnail">
<Rectangle.Fill>
<VisualBrush Visual="{Binding Content, Converter={StaticResource WorkAreaToNavigatorConverter}}"/>
</Rectangle.Fill>
</Rectangle>
<Border Background="#20000000" x:Name="ViewPort" Cursor="SizeAll"
Width="{Binding ViewportWidth}" Height="{Binding ViewportHeight}"
MaxWidth="{Binding Content.ActualWidth}" MaxHeight="{Binding Content.ActualHeight}"
HorizontalAlignment="Left" VerticalAlignment="Top"
MouseDown="Border_MouseDown"
MouseUp="Border_MouseUp"
MouseMove="Border_MouseMove">
<Border.RenderTransform>
<TranslateTransform X="{Binding HorizontalOffset}" Y="{Binding VerticalOffset}"/>
</Border.RenderTransform>
</Border>
</Grid>
</Viewbox>
</Border>
So, I bind work area's content (ScrollViewer) to navigator's DataContext. Rectangle inside Viewbox represents thumbnail of work area's Canvas. I fill Rectangle with VisualBrush, which binds to work area's Canvas. Without converter I have this:
I want to convert work area's Canvas to another Canvas, that doesn't have grid lines and contains rectangles instead of tables. So i wrote WorkAreaToNavigatorConverter:
public class WorkAreaToNavigatorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Canvas workAreaCanvas = value as Canvas;
Canvas canvas = new Canvas()
{
Width = workAreaCanvas.ActualWidth,
Height = workAreaCanvas.ActualHeight,
Background = new SolidColorBrush(Colors.White)
};
foreach (Table table in workAreaCanvas.Children.OfType<Table>())
{
Rectangle rectangle = new Rectangle()
{
Width = table.ActualWidth,
Height = table.ActualHeight,
Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString((string)table.Tag))
};
Canvas.SetLeft(rectangle, Canvas.GetLeft(table));
Canvas.SetTop(rectangle, Canvas.GetTop(table));
Canvas.SetZIndex(rectangle, Canvas.GetZIndex(table));
canvas.Children.Add(rectangle);
}
return canvas;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
Convert method is called only once at the beginning of application. Interesting that if I replace return canvas with return value it works like without converter, like it skip code before return.
Why converter doesn't work? What can I do?
Related
Goal: To align combobox popup to bottom Left. Kindly check image below for reference:
What i tried?: I tried playing with Placement property of PART_Popup. If i set Placement="Bottom", the Popup is rendering from Bottom right. Check this image below:
Also, in VS Designer it is showing correctly:
ComboBox PART_Popup:
<Popup x:Name="PART_Popup"
AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom"
MinWidth="170">
<themes:SystemDropShadowChrome x:Name="shadow" Color="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=templateRoot}">
<Border x:Name="dropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{StaticResource ComboBox.Static.Background}"
CornerRadius="3">
<ScrollViewer x:Name="DropDownScrollViewer" CanContentScroll="False">
<Grid x:Name="grid" RenderOptions.ClearTypeHint="Enabled">
<Canvas x:Name="canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
<Rectangle x:Name="opaqueRect" Fill="{Binding Background, ElementName=dropDownBorder}"
Height="{Binding ActualHeight, ElementName=dropDownBorder}" Width="{Binding ActualWidth, ElementName=dropDownBorder}"/>
</Canvas>
<ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Margin="16"/>
</Grid>
</ScrollViewer>
</Border>
</themes:SystemDropShadowChrome>
</Popup>
XAML:
<ComboBox
HorizontalAlignment="Left"
VerticalAlignment="Center"
SelectedIndex="0"
Margin="50,0"
Style="{StaticResource FlatComboBoxStyle2}">
<ComboBoxItem Content="Recent"/>
<ComboBoxItem Content="Alphabetical"/>
</ComboBox>
I am looking for XAML code solution only. Thanks.
I fixed it by adding a converter in App.xaml.cs:
public class PositiveToNegativeConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double) value > 0 ? 0 - (double) value : 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And define it in my ComboBoxTemplate:
<PositiveToNegativeConverter x:Key="PositiveToNegativeConverter" />
And call it like this in Popup's HorizontalOffest:
HorizontalOffset="{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource PositiveToNegativeConverter}}"
I hope this helps out anyone looking for solution.
I had the same issue, and after following several thread here (all my XAML trial failed, to brief up, Popup.Placement = [whatever] didn't change anything on run) I ended up with the following "solution".
Not pretty, so if you knew more elegant way I'm open.
So I named my ComboBox and Add a event handler (via XAML) on DropDownOpen (this is the dirty side imo) where I force my Popup.Placement to RelativePoint and set a VerticalOffset to my ComboBox.ActualHeight.
Here is the XAML code :
<ComboBox Name="NIC_CmbBox" HorizontalAlignment="Stretch" DropDownOpened="NIC_CmbBox_DropDownOpened">
C# :
private Popup _comboBoxPopup;
private void NIC_CmbBox_DropDownOpened(object sender, EventArgs e)
{
ComboPopupPlacement();
}
public Popup ComboBoxPopup {
get
{
if(_comboBoxPopup == null)
{
//try to get it
if(NIC_CmbBox != null)
_comboBoxPopup = (Popup)NIC_CmbBox.Template.FindName("PART_Popup", NIC_CmbBox);
}
return _comboBoxPopup;
}
set => _comboBoxPopup = value; }
public void ComboPopupPlacement()
{
//If placement haven't already be done
if (ComboBoxPopup != null && _comboBoxPopup.Placement != PlacementMode.RelativePoint)
{
_comboBoxPopup.Placement = PlacementMode.RelativePoint;
_comboBoxPopup.VerticalOffset = NIC_CmbBox.ActualHeight;
}
}
So I'm trying to pass my Rectangle as a CommandParameter because I wanna get it's X & Y properties because the goal is to move it when I press W.
How do I properly pass it as a CommandParameter?
<Window.InputBindings>
<KeyBinding Key="W" Command="{Binding Forward}" CommandParameter="{ Binding RelativeSource= { RelativeSource Mode=FindAncestor,
AncestorType={x:Type Rectangle}}}"/>
</Window.InputBindings>
<Window.DataContext>
<viewmodel:BaseViewModel />
</Window.DataContext>
<Grid>
<Grid x:Name="PlayerArea">
<Border Width="25" HorizontalAlignment="Left" Background="Green"/>
<Border Width="25" HorizontalAlignment="Right" Background="Green"/>
<Border Height="25" VerticalAlignment="Top" Background="Green">
<Button Content="Connect" Width="100" Command="{Binding ConnectCommand}"/>
</Border>
<Border Height="25" VerticalAlignment="Bottom" Background="Green"/>
<Rectangle Width="50"
Height="50"
Fill="Red"
x:Name="localPlayer"/>
</Grid>
</Grid>
In this scenario maybe you want to use a Canvas instead of a Grid.
Defines an area within which you can explicitly position child elements by using coordinates that are relative to the Canvas area.
You can bind the Rectangle as command parameter, but then you pass a view control to your view model, which should be avoided to maintain a clean separation of concerns. However it would not help here, because Rectangle itself does not have coordinates.
Nevertheless, you can create properties for the X and Y coordinates in your view model. Do not forget to implement INotifyPropertyChanged, otherwise these properties will not be updated in the user interface.
private double _x;
private double _y;
public double X
{
get => _x;
set
{
if (_x == value)
return;
_x = value;
OnPropertyChanged(nameof(X));
}
}
public double Y
{
get => _y;
set
{
if (_y == value)
return;
_y = value;
OnPropertyChanged(nameof(Y));
}
}
As your rectangle has a certain size, you have to compensate it to get the correct coordinates. You can do that in the view model, too, but I create a multi value converter as an example here, that is only used in XAML bindings.
public class SizeCompensatingCoordinateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double)value - System.Convert.ToDouble(parameter) / 2;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double)value - System.Convert.ToDouble(parameter) / 2;
}
}
I used your example and adapted it for a Canvas using the converter to set the coordinates via bindings to the X and Y properties in the view model.
<Canvas x:Name="PlayerArea" Width="200" Height="200">
<Canvas.Resources>
<local:SizeCompensatingCoordinateConverter x:Key="CoordinateWithRespectToSizeConverter"/>
</Canvas.Resources>
<Border Width="25"
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=Height}"
HorizontalAlignment="Left"
Background="Green"/>
<Border Canvas.Right="0"
Width="25"
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=Height}"
HorizontalAlignment="Right"
Background="Green"/>
<Border Height="25"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=Width}"
Background="Green">
<Button Content="Connect"
Width="100"
Command="{Binding ConnectCommand}"/>
</Border>
<Border Canvas.Bottom="0"
Height="25"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=Width}"
Background="Green"/>
<Rectangle Canvas.Top="{Binding X, Converter={StaticResource CoordinateWithRespectToSizeConverter}, ConverterParameter=50}"
Canvas.Left="{Binding Y, Converter={StaticResource CoordinateWithRespectToSizeConverter}, ConverterParameter=50}"
Width="50"
Height="50"
Fill="Red"
x:Name="localPlayer" />
</Canvas>
This is not a perfect example that might fit all your requirements, but it should provide you the basics of binding coordinates even via a custom converter to view model properties and using coordinates in a Canvas. Now, you can directly read and set the X and Y coordinates in your Forward command.
How can I clip/shape a ProgressBar indicator using a property in the ViewModel?
I want to add a feedback indicator to a Slider control using a ProgressBar, where I clip so that only a line of circles are shown. And since the View is resizeable and the circle diameter is fixed, I want to bind the clipped shape to a property indicating how many circles that are to be shown.
I tried using VisualBrush, where I tiled Elipse shapes. But it ended with the individual circles being cut, which didn't look any good.
The circle line here is what I want to achieve:
<ProgressBar x:Name="PART_FeedbackBar" Grid.Column="1" Grid.Row="1"
Value="{Binding Path=Value}"
Maximum="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Slider}}, Path=Maximum}"
Minimum="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Slider}}, Path=Minimum}"
Orientation="Horizontal"
BorderThickness="0"
Background="{StaticResource PrimaryColor}"
Foreground="{StaticResource HighlightColor}"
Height="20">
<ProgressBar.Clip>
<!-- What to add here? -->
</ProgressBar.Clip>
</ProgressBar>
Well, I found a solution using ProgressBar.OpacityMask rather than ProgressBar.Clip, since ProgressBar.OpacityMask supports VisualBrush. This way I was able to tile an Elipse, which the ProgressBar Opacity is set to 0 around.
To get the circle count to adjust to the ProgressBar width, I added a SizeChanged event handler which calculates the number of Elipse shapes. The VisualBrush Viewport property is bound to the Elipse count property, using a Converter to convert the int value to a Rect.
Result:
XAML:
<ProgressBar x:Name="PART_FeedbackBar" Grid.Column="1" Grid.Row="1"
SizeChanged="PART_FeedbackBar_SizeChanged"
Value="{Binding Path=Value}"
Maximum="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Slider}}, Path=Maximum}"
Minimum="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Slider}}, Path=Minimum}"
Orientation="Horizontal"
BorderThickness="0"
Foreground="{StaticResource HighlightColor}"
Background="{StaticResource PrimaryColor}"
Height="15"
Margin="25,10">
<ProgressBar.OpacityMask>
<VisualBrush x:Name="FeedbackBarOpacityMask" TileMode="Tile"
Viewport="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type UserControl}}, Path=FeedbackDotCount,
Converter={converters:IntegerToViewportConverter}}"
Stretch="Uniform">
<VisualBrush.Visual>
<Ellipse Height="1" Width="1" Fill="Black" />
</VisualBrush.Visual>
</VisualBrush>
</ProgressBar.OpacityMask>
</ProgressBar>
SizeChanged event handler:
private void PART_FeedbackBar_SizeChanged(object sender, SizeChangedEventArgs e)
{
FeedbackDotCount = (int)Math.Ceiling(e.NewSize.Width / 25);
}
Converter:
public class IntegerToViewportConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var count = (int)value;
var viewport = new Rect(0, 0, 0, 1);
if (count > 0)
{
viewport.Width = 1 / (double)count;
}
return viewport;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
This is C# WPF and xaml. I have main window, and I have two graphs that share this window. They are vertically arranged. They both have same width as the main window. However, I want the first graph to fill the entire window (except for some margin on the top of the window) when the second one is collapsed, and I want them to share the height (each with Height = (Height of Window)/2 ) when both are visible.
Here is what I tried in xaml, not successful though:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<d3:ChartPlotter Grid.Row="0" Name ="timeDomainPlotter" >
</d3:ChartPlotter>
<d3:ChartPlotter Grid.Row="1" Name ="freqDomainPlotter" >
</d3:ChartPlotter>
</Grid>
The first window does not take over the second window's space when the second one is Visibility.Collapsed.
How should I do this? Thanks.
Update:
Converter code in pop up window where there are two graphs:
public class VisibilityToHeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
GridLength height = new GridLength();
var visiblity = (Visibility)value;
switch (visiblity)
{
case Visibility.Collapsed:
height = new GridLength(0, GridUnitType.Auto);
break;
case Visibility.Visible:
height = new GridLength(1, GridUnitType.Star);
break;
case Visibility.Hidden:
height = new GridLength(0, GridUnitType.Auto);
break;
}
return height;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Interaction logic for SignalStatsDisplay.xaml
/// </summary>
public partial class SignalStatsDisplay : Window
{
xaml for pop up window:
<Window x:Class="FileWatcherCaller.View.SignalStatsDisplay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"
xmlns:local="clr-namespace:FileWatcherCaller.View"
Title="Real Time Signal Display" Height="409" Width="1200">
<Window.Resources>
<local:VisibilityToHeightConverter x:Key="VisToHeightConv"/>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<CheckBox Height="16" HorizontalAlignment="Left" Name="pixVal" VerticalAlignment="Top" Width="120" Checked="checkBox1_Checked">Pixel Value</CheckBox>
<CheckBox Height="16" HorizontalAlignment="Left" Name="roiMean" VerticalAlignment="Top" Width="120">ROI Mean</CheckBox>
<CheckBox Height="16" HorizontalAlignment="Left" Name="roiStd" VerticalAlignment="Top" Width="120" Checked="roiStd_Checked">ROI Std</CheckBox>
</StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding ElementName=timeDomainPlotter, Path=Visibility, Converter={StaticResource VisToHeightConv}}" Name="RowOne" />
<RowDefinition Height="{Binding ElementName=freqDomainPlotter, Path=Visibility, Converter={StaticResource VisToHeightConv}}" Name="RowTwo" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<d3:ChartPlotter Grid.Row="0" Name ="timeDomainPlotter" >
</d3:ChartPlotter>
<d3:ChartPlotter Grid.Row="1" Name ="freqDomainPlotter" >
</d3:ChartPlotter>
</Grid>
</StackPanel>
</Grid>
</Window>
In main window, how the Visibility of two graphs are initialized:
public void StartWatch()
{
if (_fileWatcher != null)
{
_fileWatcher.Dispose();
_fileWatcher = null;
}
if (InitWatcher())
{
this._fileWatcher.Start();
this.ButtonStart.IsEnabled = false;
this.ButtonStop.IsEnabled = true;
}
_signalDisplay = new SignalStatsDisplay();
if (_signalDisplay.Visibility != Visibility.Visible)
{
_signalDisplay.Show();
_signalDisplay.timeDomainPlotter.Visibility = Visibility.Visible;
_signalDisplay.freqDomainPlotter.Visibility = Visibility.Collapsed;
}
}
For Kevin's sulution, I have the xaml for the pop up window:
<Window x:Class="FileWatcherCaller.View.SignalStatsDisplay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"
Title="Real Time Signal Display" Height="409" Width="1200">
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<CheckBox Height="16" HorizontalAlignment="Left" Name="pixVal" VerticalAlignment="Top" Width="120" Checked="checkBox1_Checked">Pixel Value</CheckBox>
<CheckBox Height="16" HorizontalAlignment="Left" Name="roiMean" VerticalAlignment="Top" Width="120">ROI Mean</CheckBox>
<CheckBox Height="16" HorizontalAlignment="Left" Name="roiStd" VerticalAlignment="Top" Width="120" Checked="roiStd_Checked">ROI Std</CheckBox>
</StackPanel>
<UniformGrid Columns="1">
<d3:ChartPlotter Name ="timeDomainPlotter" >
</d3:ChartPlotter>
<d3:ChartPlotter Name ="freqDomainPlotter" >
</d3:ChartPlotter>
</UniformGrid>
</StackPanel>
</Grid>
</Window>
But still, it is not maximize the top D3 graph as expected. It is still takes only half of the window. Anything I should do in the behind code?
<Window x:Class="FileWatcherCaller.View.SignalStatsDisplay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"
Title="Real Time Signal Display" Height="409" Width="1200">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<CheckBox Height="16" HorizontalAlignment="Left" Name="pixVal" VerticalAlignment="Top" Width="120" Checked="checkBox1_Checked">Pixel Value</CheckBox>
<CheckBox Height="16" HorizontalAlignment="Left" Name="roiMean" VerticalAlignment="Top" Width="120">ROI Mean</CheckBox>
<CheckBox Height="16" HorizontalAlignment="Left" Name="roiStd" VerticalAlignment="Top" Width="120" Checked="roiStd_Checked">ROI Std</CheckBox>
</StackPanel>
<UniformGrid Columns="1" Grid.Row="1">
<d3:ChartPlotter Name ="timeDomainPlotter" >
</d3:ChartPlotter>
<d3:ChartPlotter Name ="freqDomainPlotter" >
</d3:ChartPlotter>
</UniformGrid>
</Grid>
</Window>
UniformGrid works the way you're looking for (as long as you don't want the user to resize the two sections)
<UniformGrid Columns="1">
<TextBox Visibility="Collapsed">Hello</TextBox>
<TextBox Visibility="Visible">Goodbye</TextBox>
</UniformGrid>
For something more flexible, I think you're going to have to write some code.
Here is an example app that has your desired behavior:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:VisibilityToHeightConverter x:Key="VisToHeightConv"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding ElementName=rctTop, Path=Visibility, Converter={StaticResource VisToHeightConv}}" Name="RowOne" />
<RowDefinition Height="{Binding ElementName=rctBottom, Path=Visibility, Converter={StaticResource VisToHeightConv}}" Name="RowTwo" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Fill="Black" Name="rctTop" Grid.Row="0"/>
<Rectangle Fill="Red" Name="rctBottom" Grid.Row="1"/>
</Grid>
</Window>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class VisibilityToHeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
GridLength height = new GridLength();
var visiblity = (Visibility)value;
switch(visiblity)
{
case Visibility.Collapsed:
height = new GridLength(0, GridUnitType.Auto);
break;
case Visibility.Visible:
height = new GridLength(1, GridUnitType.Star);
break;
case Visibility.Hidden:
height = new GridLength(0, GridUnitType.Auto);
break;
}
return height;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Let us know if any part of this code is unfamiliar (value converters, binding) and we'll provide an explanation.
I have a ControlElement called MultiImageDevice which is an Element that should be initialised with a List/Array of URIs for images that I want to be able to switch through.
XAML-Code for the MultiImageDevice:
<component:AbstractDevice x:Class="View.MultiImageDevice"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:component="clr-namespace:View"
x:Name="userControl"
d:DesignHeight="100" d:DesignWidth="100">
<Canvas Initialized="Level1Canvas_Initialized" Height="{Binding ElementName=userControl, Path=ActualHeight}" Width="{Binding ElementName=userControl, Path=ActualWidth}">
<Canvas Initialized="Level2FrameworkElement_Initialized">
<Image x:Name="image" Source="{Binding Path=ImageSources[0], RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type component:MultiImageDevice}}}" Height="{Binding ElementName=userControl, Path=ActualHeight}" Width="{Binding ElementName=userControl, Path=ActualWidth}">
<Image.Effect>
<DropShadowEffect BlurRadius="10" Opacity="0.5" ShadowDepth="3"/>
</Image.Effect>
</Image>
</Canvas>
</Canvas>
</component:AbstractDevice>
and the C#-Code (without using directives):
namespace View
{
public partial class MultiImageDevice : AbstractDevice
{
private double _currentImage = 1d;
internal string[] ImageURIs { get; set; }
public MultiImageDevice()
{
InitializeComponent();
}
public override void PositionChangedSignal(ComponentIdentifier identifier, double position)
{
if (_currentImage != position)
{
_currentImage = position;
//Switching shall occur here
}
}
}
}
And last but not least the call in the seperate XAML:
<component:MultiImageDevice Height="60" Canvas.Left="56.104" Canvas.Top="409.859" Width="60">
<component:MultiImageDevice.ImageURIs>
<x:Array Type="sys:String">
<sys:String>
pack://application:,,/img/AuxReleaseCancelButton_pushed.png
</sys:String>
</x:Array>
</component:MultiImageDevice.ImageURIs>
</component:MultiImageDevice>
The problem with this approach is a compiler error stating that I can't add a ArrayExtension-Type to a String[]-Type. I also tried leaving the <x:Array>-part or declaring the Array as a Resource in the MultiImageDevice, which brought up new problems. So I'm at a loss now. I would be grateful for any suggestion.
Thanks
I found this page with this example
<ListBox>
<ListBox.ItemsSource>
<x:Array Type="{x:Type sys:String}">
<sys:String>John</sys:String>
<sys:String>Paul</sys:String>
<sys:String>Andy</sys:String>
</x:Array>
</ListBox.ItemsSource>
</ListBox>
The ItemSource property is of IEnumerable type. So I changed string[] to IEnumerable (IEnumerable<string> is not working) in my dummy control and it works. The disadvantage is that you loose the type checking.
I also had problems with the plain property and I have to create an attached one for Urls.