Invalidate Thumb position when IsDirectionReversed changes - c#

I'm using a slider control in a C# WPF application and want to offer the ability for users to invert the control on the fly.
I've already established that the slider control has a built in IsDirectionReversed property which works well for changing the direction of the control (effectively swapping its minimum and maximum values to opposite ends of the control). However, when the property is changed on the fly, the thumb position remains where it is (visually implying an incorrect value) but its value remains the same (as I would expect).
(After inverting the control, clicking anywhere on the track causes the thumb to update to either +1 or -1 of its current value but repositions the thumb into its correct visual position)
Can anyone suggest a way of forcing the thumb to update its position when the inverted property is changed?
XAML
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="139" Width="303">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
</Grid.ColumnDefinitions>
<Slider Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Name="SliderControl" HorizontalAlignment="Left" VerticalAlignment="Top" Width="276" IsDirectionReversed="{Binding Inverted}" Maximum="100" Minimum="1" SmallChange="1"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="Value:" HorizontalAlignment="Left" VerticalAlignment="Top" />
<TextBlock Grid.Row="2" Grid.Column="2" Text="{Binding ElementName=SliderControl, Path=Value}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<TextBlock Grid.Row="3" Grid.Column="1" Text="Inverted:" HorizontalAlignment="Left" VerticalAlignment="Top" />
<TextBlock Grid.Row="3" Grid.Column="2" Text="{Binding ElementName=SliderControl, Path=IsDirectionReversed}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Button Grid.Row="4" Grid.Column="2" Content="Flip!" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
</Window>
C#
public partial class MainWindow : Window, INotifyPropertyChanged
{
public bool mSliderInverted = false;
public bool Inverted
{
get
{
return mSliderInverted;
}
}
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
mSliderInverted = !mSliderInverted;
OnPropertyChanged("Inverted");
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}

It seems flipping the IsDirectionInversed property doesn't cause the control to rerender, I'd say this is a bug in WPF (Bug in WPF!? No way!).
By trial and error, I discovered that if you invalidate the PART_Track part of the slider (The element that contains the RepeatButtons and the Thumb), it magically switches it around and everything works the way it should!
So if you just modify your Button_Click handler to look like this, everything should work just fine. :-)
private void Button_Click(object sender, RoutedEventArgs e)
{
mSliderInverted = !mSliderInverted;
OnPropertyChanged("Inverted");
// Retrieve the Track from the Slider control
var track = SliderControl.Template.FindName("PART_Track", SliderControl) as Track;
// Force it to rerender
track.InvalidateVisual();
}

Related

WPF - Getting position of a control keeps returning {0;0}

I'm trying to get the positions of controls (buttons) but it keeps returning {0;0}. I'm sure there's an explanation for this, but I can't figure out why this happens.
I want the position of the control, relative to the window or a certain container. My buttons are arranged in another grid. Taking the margins of these buttons would just give 0,0 since they're all inside grid cells.
What I tried:
- var point = btnTest.TransformToAncestor(mainGrid).Transform(new Point());
- UIElement container = VisualTreeHelper.GetParent(btnTest) as UIElement;
Point relativeLocation = btnTest.TranslatePoint(new Point(0, 0), mainGrid);
I tried this with a grid as a parent and with a canvas. Everything I try gives me {0,0}. When I change the new Point parameters, the position does change. It stays the same as the parameters.
Small part of my XAML:
<Grid x:Name="mainGrid">
<Grid Name="buttonGrid" Margin="105,64,98.4,97.8">
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="25"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Button x:Name="btnTest" Grid.Row="0" Grid.Column="0" Content="Button" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top" Width="26" Height="29"/>
<Button x:Name="btnTest2" Grid.Row="1" Grid.Column="1" Content="Button" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top" Width="26" Height="29"/>
</Grid>
</Grid>
Your code works perfectly fine, it is the timing that is the issue. The UI elements must be drawn before the position can be retrieved.
The code sample below shows the point extraction running in the constructor with the result 0,0 and then running in the loaded event which returns the desired result 84,78.
<Window x:Class="WpfApp7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded">
<Grid x:Name="mainGrid">
<Button x:Name="btnTest" Content="TileButton" HorizontalAlignment="Left" Margin="84,78,0,0" VerticalAlignment="Top" Width="109" Height="103"/>
</Grid>
public MainWindow()
{
InitializeComponent();
GetPoint();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
GetPoint();
}
private void GetPoint()
{
var point = btnTest.TransformToAncestor(mainGrid).Transform(new Point());
UIElement container = VisualTreeHelper.GetParent(btnTest) as UIElement;
Point relativeLocation = btnTest.TranslatePoint(new Point(0, 0), mainGrid);
MessageBox.Show($"X = {relativeLocation.X} Y = {relativeLocation.Y}");
}

Position image in Grid with ZIndex

I have an image on top of a button that is on a grid. I want to place the image behind the button when I click on the button "Hide Image". I have tried with Panel.ZIndex, but apparently it is not available for Grid.
<Grid HorizontalAlignment="Left" Height="100" Margin="201,79,0,0" VerticalAlignment="Top" Width="100">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button x:Name="btn_1" Grid.Column="0" Grid.Row="0" Panel.ZIndex="-1"/>
<Image Source="Max_dog.png" Grid.Column="0" Grid.Row="0"/>
<Button x:Name="btn_2" Grid.Column="1" Grid.Row="0"/>
<Button x:Name="btn_3" Grid.Column="0" Grid.Row="1"/>
<Button x:Name="btn_4" Grid.Column="1" Grid.Row="1"/>
</Grid>
<Button Content="Button" Margin="215,205,228.667,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
The right way to do this in WPF is to have a view model that you attach to the data context of the window or a control. Then you bind a property of the view model to the Visibility property of the image.
public class MyViewModel : INotifyPropertyChanged
{
private bool _isImageVisible;
public bool IsImageVisible
{
get { return _isImageVisible; }
set {
if (value != _isImageVisible) {
_isImageVisible = value;
OnPropertyChanged(nameof(ImageVisibility));
}
}
}
public Visibility ImageVisibility => _isImageVisible
? Visibility.Visible
: Visibility.Hidden; // or Collapsed
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
In XAML
<Image Source="Max_dog.png" Grid.Column="0" Grid.Row="0"
Visibility="{Binding ImageVisibility}"/>
Not that you cannot bind the Visibility property to a Boolean. Therefore I made two properties a Boolean property that you can easily set and another one exposing the required Visibility enum.
I found the solution
private void Button_Click(object sender, RoutedEventArgs e)
{
btn_1.SetValue(Panel.ZIndexProperty, 3);
}
WPF forms the controls in the layout on stack. The item lower in the stack will always have lower zIndex compared to item on higher position. The solution here to change the declaration of statements to
<Grid HorizontalAlignment="Left" Height="100" Margin="201,79,0,0" VerticalAlignment="Top" Width="100">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Source="Max_dog.png" Grid.Column="0" Grid.Row="0"/>
<Button x:Name="btn_1" Grid.Column="0" Grid.Row="0"/>
<Button x:Name="btn_2" Grid.Column="1" Grid.Row="0"/>
<Button x:Name="btn_3" Grid.Column="0" Grid.Row="1"/>
<Button x:Name="btn_4" Grid.Column="1" Grid.Row="1"/>
</Grid>
<Button Content="Button" Margin="215,205,228.667,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
See the declaration of button and image has been reversed.

WPF adaptative user interface

Hi fellow programmers,
I'm working on a WPF software that uses a Canvas to display and move graphic objects.
When the user clic on an object, I need to display a panel with the selected object's properties.
These properties are different for each object, one can have a displayed text, another a background color or a scale value.
What is the best way to program this ?
I have 9 objects type, I'm searching for something more elegant than creating my controls in panels and switch betwenn then for every graphic object type.
Thank you for your help.
Edit - to show design code :
The dock panel for generated Wpf controls to display properties.
<DockPanel x:Name="pnlProperties" Width="200" Grid.Column="2" Background="red">
<Grid x:Name="GridProperties" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Margin="0,2,0,25" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
<RowDefinition Height="95"/>
<RowDefinition Height="95"/>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ***** Label ***** -->
<Label x:Name="lblLabel1" Content="test Prop" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="0" Grid.Column="0" FontSize="16"/>
<Label x:Name="lblLabel2" Content=" Prop 2" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="1" Grid.Column="0" FontSize="16"/>
<Label x:Name="lblLabel3" Content=" Prop 3" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="2" Grid.Column="0" FontSize="16"/>
<Label x:Name="lblLabel4" Content=" Prop 4" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="3" Grid.Column="0" FontSize="16"/>
</Grid>
</Grid>
</DockPanel>
The Canvas that displays the MovableObject (userControl) of each graphic objects :
<UserControl x:Class="DashEditor.Views.ScreenView"
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:DashEditor.Views">
<Canvas x:Name="ObjectsCanvas" HorizontalAlignment="Center" VerticalAlignment="Center" Width="800" Height="480" AllowDrop="True" Background="Black" PreviewMouseLeftButtonDown="ObjectsCanvas_PreviewMouseLeftButtonDown" >
<Image x:Name="imgFond" Stretch="Fill" Source="/DashEditor;component/assets/FondXAP.png" Width="800" Height="480"/>
</Canvas>
One of the graphic object class :
[StructLayout(LayoutKind.Sequential)]
[XmlRoot("XapLabel")]
public class XapLabel : IXapGraphicObject
{
private int _id;
public int Id
{
get
{
return _id;
}
set
{
_id = value;
}
}
[XmlIgnore]
private MovableObject _Control;
[XmlIgnore]
public MovableObject Control
{
get
{
return _Control;
}
set
{
_Control = value;
}
}
private Point _pos;
public Point Pos
{
get
{
return _pos;
}
set
{
_pos = value;
}
}
public IXapGraphicObject getXapParent(MovableObject Control)
{
return this;
}
public ObjectType Type
{
get
{
return ObjectType.Label;
}
}
public XapLabel()
{
}
public void ConnectToMoveEvent()
{
_Control.OnObjectTranslating += _Control_OnObjectTranslating;
}
private void _Control_OnObjectTranslating(Vector displacement)
{
Pos = Pos + displacement;
}
}
I've done something similar to this, if you are familiar with MVVM:-
For the canvas, I used an ItemsControl bound to an ObservableCollection of your "graphic objects", to which you'll be adding objects that you want to appear on the canvas. You'll also need to change the ItemsControl's panel template to a Canvas:-
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="800" Height="480" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Your "graphic object" classes need to expose double properties (say "X" and "Y"), to control the object's position on the canvas.
Next, create a XAML DataTemplate for each of these classes, to define their visual appearance. The data template should include the following bindings:
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
For the property grid, rather than write your own, look at the free Xceed Toolkit community edition (here), which has a very good PropertyGrid control. You bind its SelectedObject property to the selected object, but read the documentation - there are plenty of decent features.
(If you are using MVVM then remember to change your classes to implement INotifyPropertyChanged, and raise the PropertyChanged event in the setters).
For the drag and drop functionality, you should just be able to set the selected object's X and Y values within the mouse move event.
Not a full solution I know, but will hopefully point you in the right direction.

Unable to see my controls in MainPage.g.cs of my MainPage.xaml

I have a simple question about XAML.
I created a grid element inside another grid and added a textblock and a web browser in it. However, I am unable to access it in MainPage.xaml.cs using their name (e.g. this.LastName) doesn't work. On further debugging, I saw that they are not declared in MainPage.g.cs. Since MainPage.g.cs is auto defined, I don't know how to fix it. Can someone help please? Below is my C# and XAML code. Thanks!
========================================================
public partial class MainPage : Microsoft.Phone.Controls.PhoneApplicationPage {
internal System.Windows.Controls.Grid LayoutRoot;
internal System.Windows.Controls.Grid ContentPanel;
internal Microsoft.Phone.Controls.LongListSelector MainLongListSelector;
internal System.Windows.Controls.Image RefreshIcon;
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Windows.Application.LoadComponent(this, new System.Uri("/Suod;component/MainPage.xaml", System.UriKind.Relative));
this.LayoutRoot = ((System.Windows.Controls.Grid)(this.FindName("LayoutRoot")));
this.ContentPanel = ((System.Windows.Controls.Grid)(this.FindName("ContentPanel")));
this.MainLongListSelector = ((Microsoft.Phone.Controls.LongListSelector)(this.FindName("MainLongListSelector")));
this.RefreshIcon = ((System.Windows.Controls.Image)(this.FindName("RefreshIcon")));
}
}
========================================================
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<phone:LongListSelector x:Name="MainLongListSelector" Margin="-12,-97,0,97" ItemsSource="{Binding Items}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="12,100,0,45">
<Grid x:Name="CompanyContentGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Name="LastName" Text="{Binding Name}" TextWrapping="Wrap" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
<phone:WebBrowser Grid.Row="5" Height="400" Name="WebBrowser" Margin="12,-6,24,0" FontFamily="Portable User Interface"/>
</Grid>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
<Grid Grid.Row="2" >
<Image Source="/Assets/Refresh.png" Name="RefreshIcon" Width="80" Height="80" Tap="Image_Tap"/>
</Grid>
I think the problem is that your textblock i too deep in xaml code. I think Data Binding should be enough in this situation but there is a way to go around this problem. You have to subscribe this element to some event(like Loaded) and save sender to some property in code behind:
<TextBlock Loaded="LastName_Loaded" Grid.Row="0" Name="LastName" Text="{Binding Name}" TextWrapping="Wrap" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
public TextBlock lastName;
private void LastName_Loaded(object sender, RoutedEventArgs e)
{
lastName= (TextBlock)sender;
}
Now you are able to access this element in code behind.

How to get a Grid to raise MouseDown events when no UIElemets in cells clicked?

I observe Mouse.MouseDown and PreviewMouseLeftButtonDown are only raised when the mouse is clicked in a cell that contains a UIElement, e.g.:
XAML
<Window xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="WpfApplication5.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="300" Height="300">
<Grid Mouse.MouseDown="Grid_MouseDown" PreviewMouseLeftButtonDown="Grid_PreviewMouseLeftButtonDown">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition Height="100"/>
<RowDefinition Height="100"/>
</Grid.RowDefinitions>
<Rectangle Grid.Column="2" Grid.RowSpan="2" Fill="Red"/>
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Grid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("PreviewMouseLeftButtonDown raised!");
}
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("MouseDown raised!");
}
}
The lines are only written to Debug when the red rectangle is clicked. How can I get the events, or at least one of them, to be raised the grid contains nothing?
I do not know the cause of the issue, but as a workaround giving the Grid a background, even Transparent, seems to make the events be raised!
<Grid Background="Transparent" Mouse.MouseDown="Grid_MouseDown" PreviewMouseLeftButtonDown="Grid_PreviewMouseLeftButtonDown">

Categories