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;
}
}
Related
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.
I've got one page in my WPF app that should display some "tiles" in number as I specify before. Tile looks like this:
So my page should look something like this:
It is achievable of course by manually cloning tiles, but I want to avoid this (achieve it in more programmatic way). So instead of creating 6 clones I should stick to only one and then if needed add remaining ones. How can I accomplish that? I guess I should create my own UserControl like this:
<Grid HorizontalAlignment="Left" Height="199" VerticalAlignment="Top" Width="207" Background="Black">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="0*"/>
</Grid.RowDefinitions>
<Image x:Name="image1" HorizontalAlignment="Left" Height="175" VerticalAlignment="Top" Width="207" Stretch="UniformToFill"/>
<Grid HorizontalAlignment="Left" Height="30" VerticalAlignment="Top" Width="112" Background="#FFC78D10">
<TextBox IsReadOnly = "True" x:Name="CategoryOfEvent" Height="30" TextWrapping="Wrap" Text="Category" Width="112" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" FontSize="18" SelectionBrush="{x:Null}" HorizontalAlignment="Left" VerticalAlignment="Top" >
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
</Grid>
<TextBox IsReadOnly = "True" x:Name="HourOfEvent" HorizontalAlignment="Left" Height="28" Margin="0,42,0,0" TextWrapping="Wrap" Text="Hour" VerticalAlignment="Top" Width="148" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="#FFE2E2E2" FontSize="22" SelectionBrush="{x:Null}" FontWeight="Bold" TextChanged="HourOfEvent_TextChanged">
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
<TextBox IsReadOnly = "True" x:Name="TitleOfEvent" HorizontalAlignment="Left" Height="88" Margin="0,82,0,0" TextWrapping="Wrap" Text="Title" VerticalAlignment="Top" Width="207" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" FontSize="20" SelectionBrush="{x:Null}" FontWeight="Bold">
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
<TextBox IsReadOnly = "True" x:Name="PlaceOfEvent" HorizontalAlignment="Left" Height="24" Margin="0,175,0,0" TextWrapping="Wrap" Text="Where" VerticalAlignment="Top" Width="207" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" FontSize="14" SelectionBrush="{x:Null}">
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
</Grid>
and just add them to my page. I would like also to mention that in every tiles there are 4 textboxes which are displaying some data parsed from Json, so maybe some automatic binding should do the job?
It is as simple as that.Firstly,what you can do is,create a UserControl with all your controls inside like TextBlocks and others.Then,decide which type of container control you want to use to hold your UserControl.Let's assume it's a grid.You can specify/set grid's column/rows for each user control.A sample :
private void addControl()
{
UserControl1 MyCon = new UserControl1;
MyGrid.Children.Add(MyCon);
Grid.SetRow(MyCon , 1); ////replace 1 with required row count
}
You can create grid rows in design time,or u can do it in code behind as well :
MyGrid.RowDefinitions.Add(new RowDefinition);
If you want to use columns instead,just apply same code but change Row/Rowdefinition with Column/ColumnDefinition
Hope this helps :)
The follwing example shows how to create multiple of the tiles you have been posting using a DataTemplate and WrapPanel. The DataTemplate specifies how an object (in this case a TileItem) is visualized. You can create multiple TileItems and then add them to an collection, in order to visualize them all.
Assuming your UI resides in MainWindow, you can create a collection with three items in it.
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TileItemCollection = new ObservableCollection<TileItem>(new []
{
new TileItem(){Category = "Alpha", Hour = "10", Title = "Hello World", Where = "Office"},
new TileItem(){Category = "Beta", Hour = "15", Title = "Test", Where = "Home"},
new TileItem(){Category = "Gamma", Hour = "44", Title = "My Title", Where = "Work"},
});
DataContext = this;
}
public ObservableCollection<TileItem> TileItemCollection { get; }
}
You could load your Items from JSON and create an TileItem for each one in the JSON document. The class for TileItemss can be found below.
public class TileItem : INotifyPropertyChanged
{
private string _hour;
private string _title;
private string _where;
private string _category;
public string Category
{
get => _category;
set
{
if (value == _category) return;
_category = value;
OnPropertyChanged();
}
}
public string Hour
{
get => _hour;
set
{
if (value == _hour) return;
_hour = value;
OnPropertyChanged();
}
}
public string Title
{
get => _title;
set
{
if (value == _title) return;
_title = value;
OnPropertyChanged();
}
}
public string Where
{
get => _where;
set
{
if (value == _where) return;
_where = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Note that in order for datachanges to be propagated to the UI, all properties which should be updated in the UI when you update them in code need to raise the property changed event. In this example all properties do this by default.
You can then update the XAML to bind to a collection. The ItemsControl acts as a container for the tiles. If you scroll down further you may notice the use of WrapPanel which is responsible for the item wrapping effect when you resize the control.
<ItemsControl ItemsSource="{Binding TileItemCollection}" Margin="20">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:TileItem}" >
<Grid HorizontalAlignment="Left" Height="199" VerticalAlignment="Top" Width="207" Background="Black">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="0*"/>
</Grid.RowDefinitions>
<Image x:Name="image1" HorizontalAlignment="Left" Height="175" VerticalAlignment="Top" Width="207" Stretch="UniformToFill"/>
<Grid HorizontalAlignment="Left" Height="30" VerticalAlignment="Top" Width="112" Background="#FFC78D10">
<TextBox IsReadOnly="True" Height="30" TextWrapping="Wrap" Text="{Binding Path=Category}" Width="112" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" FontSize="18" SelectionBrush="{x:Null}" HorizontalAlignment="Left" VerticalAlignment="Top" >
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
</Grid>
<TextBox IsReadOnly="True" HorizontalAlignment="Left" Height="28" Margin="0,42,0,0" TextWrapping="Wrap" Text="{Binding Path=Hour}" VerticalAlignment="Top" Width="148" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="#FFE2E2E2" FontSize="22" SelectionBrush="{x:Null}" FontWeight="Bold">
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
<TextBox IsReadOnly="True" HorizontalAlignment="Left" Height="88" Margin="0,82,0,0" TextWrapping="Wrap" Text="{Binding Path=Title}" VerticalAlignment="Top" Width="207" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" FontSize="20" SelectionBrush="{x:Null}" FontWeight="Bold">
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
<TextBox IsReadOnly="True" x:Name="PlaceOfEvent" HorizontalAlignment="Left" Height="24" Margin="0,175,0,0" TextWrapping="Wrap" Text="{Binding Path=Where}" VerticalAlignment="Top" Width="207" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" FontSize="14" SelectionBrush="{x:Null}">
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer>
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
Each Tile is bound to an TileItem which means that the Bindings which point to e.g. Category, point to the Category of an TileItem.
To increase reusability it would be possible to move the code into its own usercontrol and optionally to add DependencyPropertys for better control.
I'm working on a custom WPF combobox control with a template shown in the image below.
As you can see, there is a TextBox (for filtering) and a button (for creating new record). So I create the class AdvComboBox with two events Search, CreateNew. My question is : How to call the handlers in the AdvComboBox class of these events from the control template ?
public class AdvComboBox : ComboBox
{
public event TextChangedEventHandler Search;
protected virtual void OnSearch(TextChangedEventArgs e)
{
TextChangedEventHandler handler = Search;
if (handler != null) handler(this, e);
}
public event EventHandler CreateNew;
protected virtual void OnCreateNew()
{
EventHandler handler = CreateNew;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
The Popup Part :
<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">
<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="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="35"/>
<RowDefinition Height="*"/>
<RowDefinition Height="35"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"
Orientation="Horizontal">
<!--TODO : Should Call OnSearch-->
<TextBox Width="230"
Margin="5 0 0 0"
Height="26"
VerticalContentAlignment="Center">
</TextBox>
</StackPanel>
<ScrollViewer Grid.Row="1" x:Name="DropDownScrollViewer">
<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}"/>
</Grid>
</ScrollViewer>
<StackPanel Grid.Row="2"
Margin="5">
<!--TODO : Should Call OnCreateNew-->
<Button Content="Create new record"
Name="BnCreateNew"
Width="Auto"
Padding="3"
HorizontalAlignment="Right">
</Button>
</StackPanel>
</Grid>
</Border>
</themes:SystemDropShadowChrome>
</Popup>
AdvComboBox in Xaml
<local:AdvComboBox
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="250"
Height="30"
VerticalContentAlignment="Center"
Style="{DynamicResource AdvComboBoxStyle1}"
Search="AdvComboBox_OnSearch"
CreateNew="AdvComboBox_OnCreateNew">
<ComboBoxItem Content="Item 1"/>
<ComboBoxItem Content="Item 2"/>
<ComboBoxItem Content="Item 3"/>
<ComboBoxItem Content="Item 4"/>
<ComboBoxItem Content="Item 5"/>
</local:AdvComboBox>
EDIT :
After reading the msdn article provided in #Marco response, I've used Routed events to solve my problem by subscribing to the TextBox.TextChanged event and Button.Click event. Some little logic were necessary in the Button.Click handler to differentiate between the click on the ToggleButton and the simple Button (Create new record).
<local:AdvComboBox
x:Name="CbCountries"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="250"
Height="30"
VerticalContentAlignment="Center"
Style="{DynamicResource AdvComboBoxStyle1}"
TextBox.TextChanged = "AdvComboBox_OnSearch"
Button.Click = "AdvComboBox_OnCreateNew"/>
Handlers
private void AdvComboBox_OnCreateNew(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is ToggleButton)
return;
MessageBox.Show("Create new record !", "Hello");
}
private void AdvComboBox_OnSearch(object sender, RoutedEventArgs e)
{
var combobox = sender as AdvComboBox;
if (combobox == null)
return;
var textBox = e.OriginalSource as TextBox;
if (textBox == null)
return;
var itemsViewOriginal = (CollectionView)CollectionViewSource.GetDefaultView(combobox.ItemsSource);
itemsViewOriginal.Filter = (o =>
{
if (String.IsNullOrEmpty(textBox.Text))
return true;
if (((string)o).Contains(textBox.Text))
return true;
return false;
});
itemsViewOriginal.Refresh();
}
you need to cleare the events as RoutedEvents, the same way you need DependencyProperty. Something like this:
public static readonly RoutedEvent SearchEvent = EventManager.RegisterRoutedEvent(
"Search", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AdvComboBox));
// Provide CLR accessors for the event
public event RoutedEventHandler Search
{
add { AddHandler(SearchEvent , value); }
remove { RemoveHandler(SearchEvent, value); }
}
Creating coltrols is not that simple. You need also to add a template for your control. And add a static constructor:
static AdvComboBox ()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(AdvComboBox), new FrameworkPropertyMetadata(typeof(AdvComboBox)));
}
And, add some attributes like:
[TemplatePart(Name = "PART_EditableTextBox", Type = typeof(TextBox))]
[TemplatePart(Name = "PART_Popup", Type = typeof(Popup))]
[TemplatePart(Name = "PART_CreateNewButton", Type = typeof(Button))]
[TemplatePart(Name = "PART_SearchTextBox ", Type = typeof(TextBox))]
[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(ComboBoxItem))]
public class AdvComboBox : ComboBox
{ ...
Check https://msdn.microsoft.com/en-us/library/cc295235.aspx for more information.
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?
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...