How to sync Ctrl+mousewheel across controls? - wpf

I have a class which handles a scrollbar PreviewMouseWheel event which zooms a timeline control, and everything works perfectly, except that I have to be over the scrollbar for the clr+mouse zoom to visually change the state of the scrollbar thumb. I have implemented the same PreviewMouseWheel event in the timeline control which has a different class for its datacontext, which zooms, but does not visually update the adjacent scrollbar control. What would be the best approach to handle the clr+mouse across the entire window?
public void ZoomScrollbar_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
FrameworkElement el;
el = (FrameworkElement)sender;
if (Keyboard.Modifiers == ModifierKeys.Control)
{
var newValue = InitialZoomValue;
if(e.Delta > 0)
{
newValue += 1;
}
else
{
newValue -= 1;
}
OnZoomScroll(newValue);
InitialZoomValue = newValue;
}
}
<Style x:Key="TimelineToolboxStyle" TargetType="{x:Type controls:TimelineToolbox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:TimelineToolbox}">
<Grid x:Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="*" />
<RowDefinition Height="10" />
</Grid.RowDefinitions>
<ScrollBar x:Name="ZoomScrollbar"
Grid.Row="1"
Minimum="1"
Maximum="12"
Value="{TemplateBinding InitialZoomValue}"
SmallChange="1"
Style="{DynamicResource ZoomScrollBarStyle}"
BorderThickness="9,0"
Width="22"
Margin="0"
BorderBrush="{x:Null}"
Background="Black"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

I was able to get the mouse wheel to sync with the scrollbar by calling the scrollbar's PreviewMouseWheel event handler from code behind.
private void TimelineBand_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (Keyboard.Modifiers == ModifierKeys.Control)
{
TimeLineToolBoxControl.ZoomScrollbar_PreviewMouseWheel(sender, e);
}
}

Related

DragMove() will make the Border with cornerRadius lose its mouseover trigger state?

I have created a borderless window with rounded corners, and added the drag event and a trigger to it. Here is the simple code:
<Window x:Class="DebugTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DebugTest"
mc:Ignorable="d" Height="200" Width="200"
AllowsTransparency="True" WindowStyle="None" Background="Transparent">
<Border x:Name="MainBorder" CornerRadius="15" Background="White" BorderBrush="Black" BorderThickness="1">
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MainBorder,Path=IsMouseOver}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Button Content="x" HorizontalAlignment="Right" VerticalAlignment="Top"
Margin="5" Height="20" Width="20" Click="Button_Click"/>
</Grid>
</Border>
</Window>
public MainWindow()
{
InitializeComponent();
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
this.DragMove();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
But when I run the exe file, click on the blank area within the window, the button will appear very obvious flickering situation.
Strangely enough, this situation hardly occurs when debugging in Visual Studio instead of double click the file, and it also doesn't happen while CornerRadius="0".
It looks like it lost the mouseover trigger on click, but I can't think of any good way to avoid flicker appearing, and to satisfy the need for both with rounded corners, draggable, and with trigger.
Well, although I don't know why only rounded corners would cause DragMove() to trigger the MouseLeave event, I circumvented this with background code instead of using xaml trigger.
<Border x:Name="MainBorder" CornerRadius="15" Background="White"
BorderBrush="Black" BorderThickness="1"
MouseEnter="MainBorder_MouseEnter" MouseLeave="MainBorder_MouseLeave">
<Grid Visibility="Hidden" x:Name="TriggerBorder">
<Button Content="x" HorizontalAlignment="Right" VerticalAlignment="Top"
Margin="5" Height="20" Width="20" Click="Button_Click"/>
</Grid>
</Border>
bool dragMoving = false;
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
dragMoving = true;
this.DragMove();
dragMoving = false;
}
private void MainBorder_MouseEnter(object sender, MouseEventArgs e)
{
TriggerBorder.Visibility = Visibility.Visible;
}
private void MainBorder_MouseLeave(object sender, MouseEventArgs e)
{
if (dragMoving) return;
TriggerBorder.Visibility = Visibility.Hidden;
}
Seems to work fine.

DataGrid custom CanUserAddRows implementation

I want to implement a custom add row button to a DataGrid (it's a long story). I added the button in template, and define an attached property, and I can get the button's click. But I can not add a new row in a generic way -not for a specified type-. I know that I can do similar thing in ViewModel, but I'm looking to do this in templates and attached properties. Here is my try; Any idea to complete this?
XAML:
<Style x:Key="{x:Type DataGrid}" TargetType="{x:Type DataGrid}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGrid}">
<Border x:Name="border">
<ScrollViewer x:Name="DG_ScrollViewer" Focusable="false">
<ScrollViewer.Template>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button Focusable="false"
Command="{x:Static DataGrid.SelectAllCommand}" />
<DataGridColumnHeadersPresenter
x:Name="PART_ColumnHeadersPresenter"
Grid.Column="1" />
<Grid Grid.ColumnSpan="2" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="22" />
</Grid.RowDefinitions>
<ScrollContentPresenter
x:Name="PART_ScrollContentPresenter" />
<!-- THIS IS MY CUSTOM BUTTON TO ADD NEW ROW -->
<Button x:Name="PART_AddRowButton"
Content="Add"/>
</Grid>
<ScrollBar x:Name="PART_VerticalScrollBar"/>
<Grid Grid.Column="1" Grid.Row="2">
<ScrollBar x:Name="PART_HorizontalScrollBar"/>
</Grid>
</Grid>
</ControlTemplate>
</ScrollViewer.Template>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
C#:
public class DataGridHelper {
public static readonly DependencyProperty CanUserAddRowsProperty
= DependencyProperty.RegisterAttached(
"CanUserAddRows", typeof(bool), typeof(DataGridHelper),
new FrameworkPropertyMetadata(default(bool), CanUserAddRowsChanged));
[AttachedPropertyBrowsableForType(typeof(DataGrid))]
public static bool GetCanUserAddRows(DependencyObject obj) {
return (bool)obj.GetValue(CanUserAddRowsProperty);
}
[AttachedPropertyBrowsableForType(typeof(DataGrid))]
public static void SetCanUserAddRows(DependencyObject obj, bool value) {
obj.SetValue(CanUserAddRowsProperty, value);
}
private static void CanUserAddRowsChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e) {
var dataGrid = d as DataGrid;
if (dataGrid == null)
return;
bool oldValue = (bool)e.OldValue,
newValue = (bool)e.NewValue;
if (newValue == oldValue)
return;
if (newValue) {
dataGrid.Loaded += CanUserAddRowsDataGridLoaded;
} else {
dataGrid.Loaded -= CanUserAddRowsDataGridLoaded;
}
}
private static void CanUserAddRowsDataGridLoaded(object sender, RoutedEventArgs e) {
var dataGrid = sender as DataGrid;
if (dataGrid == null)
return;
if (dataGrid.Style == null)
return;
var rootTemplate = dataGrid.Template;
if (rootTemplate == null)
return;
var scroll = rootTemplate.FindName("DG_ScrollViewer", dataGrid) as ScrollViewer;
if (scroll == null)
return;
var scrollTemplate = scroll.Template;
if (scrollTemplate == null)
return;
var button = scrollTemplate.FindName("PART_AddRowButton", scroll) as ButtonBase;
if (button == null)
return;
if (GetCanUserAddRows(dataGrid)) {
button.Click += AddRowClicked;
} else {
button.Click -= AddRowClicked;
}
}
private static void AddRowClicked(object sender, RoutedEventArgs e) {
var button = ((ButtonBase)sender);
var parent = VisualTreeHelper.GetParent(button);
while (!(parent is DataGrid))
parent = VisualTreeHelper.GetParent(parent);
var source = ((DataGrid)parent).Items.Add(...) // now what???
}
}
Well, as you can see, I got access to DataGrid after button got clicked; But what is next? How can I force the DataGrid to show NewItemPlaceHolder?
Typically in WPF, we bind collections (preferably collections that support change notification like ObservableCollection) of data objects to UI controls. Rather than adding new items to the UI controls, we add the items to the collections in the code behind/view model. As long as the collection support change notification, the UI controls will be automatically updated.
So to add a new row to your DataGrid, you need to add a new item to your collection:
dataCollection.Add(new DataType());
You should be able to access your data bound collection in your AttachedProperty using:
var dataCollection = (DataCollectionType)dataGrid.ItemsSource;
I believe that you can also use:
dataGrid.Items.Add(new DataType());
although this method is not recommended.

Child elements of scrollviewer preventing scrolling with mouse wheel?

I'm having a problem getting mouse wheel scrolling to work in the following XAML, which I have simplified for clarity:
<ScrollViewer
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
CanContentScroll="False"
>
<Grid
MouseDown="Editor_MouseDown"
MouseUp="Editor_MouseUp"
MouseMove="Editor_MouseMove"
Focusable="False"
>
<Grid.Resources>
<DataTemplate
DataType="{x:Type local:DataFieldModel}"
>
<Grid
Margin="0,2,2,2"
>
<TextBox
Cursor="IBeam"
MouseDown="TextBox_MouseDown"
MouseUp="TextBox_MouseUp"
MouseMove="TextBox_MouseMove"
/>
</Grid>
</DataTemplate>
</Grid.Resources>
<ListBox
x:Name="DataFieldListBox"
ItemsSource="{Binding GetDataFields}"
SelectionMode="Extended"
Background="Transparent"
Focusable="False"
>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style
TargetType="ListBoxItem"
>
<Setter
Property="Canvas.Left"
Value="{Binding dfX}"
/>
<Setter
Property="Canvas.Top"
Value="{Binding dfY}"
/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</ScrollViewer>
Visually, the result is an area of some known size where DataFields read from a collection can be represented with TextBoxes which have arbitrary position, size, et cetera. In cases where the ListBox's styled "area" is too large to display all at once, horizontal and vertical scrolling is possible, but only with the scroll bars.
For better ergonomics and sanity, mouse wheel scrolling should be possible, and normally ScrollViewer would handle it automatically, but the ListBox appears to be handing those events such that the parent ScrollViewer never sees them. So far I have only been able to get wheel scrolling working be setting IsHitTestVisible=False for either the ListBox or the parent Grid, but of course none of the child element's mouse events work after that.
What can I do to ensure the ScrollViewer sees mouse wheel events while preserving others for child elements?
Edit: I just learned that ListBox has a built-in ScrollViewer which is probably stealing wheel events from the parent ScrollViewer and that specifying a control template can disable it. I'll update this question if that resolves the problem.
You can also create a behavior and attach it to the parent control (in which the scroll events should bubble through).
// Used on sub-controls of an expander to bubble the mouse wheel scroll event up
public sealed class BubbleScrollEvent : Behavior<UIElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseWheel += AssociatedObject_PreviewMouseWheel;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewMouseWheel -= AssociatedObject_PreviewMouseWheel;
base.OnDetaching();
}
void AssociatedObject_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
var e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
e2.RoutedEvent = UIElement.MouseWheelEvent;
AssociatedObject.RaiseEvent(e2);
}
}
<SomePanel>
<i:Interaction.Behaviors>
<viewsCommon:BubbleScrollEvent />
</i:Interaction.Behaviors>
</SomePanel>
Specifying a ControlTemplate for the Listbox which doesn't include a ScrollViewer solves the problem. See this answer and these two MSDN pages for more information:
ControlTemplate
ListBox Styles and Templates
Another way of implementing this, is by creating you own ScrollViewer like this:
public class MyScrollViewer : ScrollViewer
{
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
var parentElement = Parent as UIElement;
if (parentElement != null)
{
if ((e.Delta > 0 && VerticalOffset == 0) ||
(e.Delta < 0 && VerticalOffset == ScrollableHeight))
{
e.Handled = true;
var routedArgs = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
routedArgs.RoutedEvent = UIElement.MouseWheelEvent;
parentElement.RaiseEvent(routedArgs);
}
}
base.OnMouseWheel(e);
}
}
I know it's a little late but I have another solution that worked for me. I switched out my stackpanel/listbox for an itemscontrol/grid. Not sure why the scroll events work properly but they do in my case.
<ScrollViewer VerticalScrollBarVisibility="Auto" PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
<StackPanel Orientation="Vertical">
<ListBox ItemsSource="{Binding DrillingConfigs}" Margin="0,5,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
became
<ScrollViewer VerticalScrollBarVisibility="Auto" PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding DrillingConfigs}" Margin="0,5,0,0" Grid.Row="0">
<ItemsControl.ItemTemplate>
<DataTemplate>
isHitTestVisible=False in the child works great for me
Edit This isnt a good way to do it

WPF - Custom design volume control

I have been working with WPF for some time.
I need to create the following control over Internet, but could not find appropriate.
Can anybody help how to implement this functionality. Value should be increasing or decreasing when clicked on control.
I found that I can use either Volume control or Slider, but not getting clear what I should use.
Thanks in anticipation.
I prefer to use a Progressbar for these kind of displays.
This is my implementation of a simple volume control looking pretty much like the one you show as an example:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private double _volume;
private bool mouseCaptured = false;
public double Volume
{
get { return _volume; }
set
{
_volume = value;
OnPropertyChanged("Volume");
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void MouseMove(object sender, MouseEventArgs e)
{
if (Mouse.LeftButton == MouseButtonState.Pressed && mouseCaptured)
{
var x = e.GetPosition(volumeBar).X;
var ratio = x/volumeBar.ActualWidth;
Volume = ratio*volumeBar.Maximum;
}
}
private void MouseDown(object sender, MouseButtonEventArgs e)
{
mouseCaptured = true;
var x = e.GetPosition(volumeBar).X;
var ratio = x / volumeBar.ActualWidth;
Volume = ratio * volumeBar.Maximum;
}
private void MouseUp(object sender, MouseButtonEventArgs e)
{
mouseCaptured = false;
}
#region Property Changed
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And the XAML:
<Window x:Class="VolumeControlApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="196" Width="319">
<Window.Resources>
<Style x:Key="VolumeStyle" TargetType="{x:Type ProgressBar}">
<Setter Property="Foreground" Value="#FFB00606"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ProgressBar}">
<Grid x:Name="TemplateRoot">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"/>
<Rectangle x:Name="PART_Track"/>
<Grid x:Name="PART_Indicator" ClipToBounds="True" HorizontalAlignment="Left">
<Rectangle x:Name="Indicator" Fill="{TemplateBinding Foreground}" RadiusX="5" RadiusY="3"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Background="#FF363636">
<Border Background="Gray" BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" CornerRadius="3" Padding="2">
<Border Background="Black" CornerRadius="3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Vol:" Foreground="White" VerticalAlignment="Center" Margin="4 0"/>
<ProgressBar x:Name="volumeBar" Height="10"
Value="{Binding Volume}"
Width="100"
MouseMove="MouseMove"
MouseDown="MouseDown"
MouseUp="MouseUp" Style="{DynamicResource VolumeStyle}" Grid.Column="1"/>
</Grid>
</Border>
</Border>
</Grid>
</Window>
You could use a slider and create a template for it.
If you need special mouse handling you'll need to subclass the slider and add logic/event handling.
The standard Slider template has a couple of repeat buttons. By simply making the left repeat button red you have a very basic implementation of the required control.
Take a look at this posts hope it helps you..
Link:
1: Sliders
2: Similar to VLC player volume control

Xaml name scope

I need to change Rectangle attribute from C# (his RotateTransform angle)
The problem is the rectangle was declared in XAML, and in the C# code is out of scope for the rectangle, I tried to use Name, and X:Name without succeed,
How should I do it?
-------------Edit--------------------
<ContentControl Width="5" Height="400" Canvas.Top="80" Canvas.Left="350"
Template="{StaticResource DesignerItemTemplateLine}">
<Rectangle Fill="Blue" IsHitTestVisible="False" Name="mRect">
<Rectangle.RenderTransform>
<TransformGroup>
<RotateTransform Angle="45"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</ContentControl>
C# code
private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
Control designerItem = this.DataContext as Control;
if (designerItem != null)
{
double left = Canvas.GetLeft(designerItem);
double top = Canvas.GetTop(designerItem);
if (mRect != null)// This line don't compile
Canvas.SetLeft(designerItem, left + e.HorizontalChange);
Canvas.SetTop(designerItem, top + e.HorizontalChange);
//Canvas.SetTop(designerItem, top + e.VerticalChange);
}
}
You can notice that "if (mRect != null)" does not pass compilation
-------------Seconde Edit--- All the code----------------------------
<Window x:Class="DiagramDesigner.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:DiagramDesigner"
WindowStartupLocation="CenterScreen"
Title="Move"
Height="550" Width="750">
<!-- MoveThumb Template -->
<ControlTemplate x:Key="MoveThumbTemplate" TargetType="{x:Type s:MoveThumbUpDwon}">
<Rectangle Fill="Transparent"/>
</ControlTemplate>
<!-- MoveThumb Template -->
<ControlTemplate x:Key="MoveThumbTemplateLeftRight" TargetType="{x:Type s:MoveThumbLeftRight}">
<Rectangle Fill="Transparent"/>
</ControlTemplate>
<!-- MoveThumb Template -->
<ControlTemplate x:Key="MoveLineTemplate" TargetType="{x:Type s:MoveLine}">
<Rectangle Fill="Black">
<Rectangle.RenderTransform>
<TransformGroup>
<RotateTransform Angle="45"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</ControlTemplate>
<!-- Designer Item Template-->
<ControlTemplate x:Key="DesignerItemTemplate" TargetType="ContentControl">
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<s:MoveThumbUpDwon Template="{StaticResource MoveThumbTemplate}" Cursor="SizeAll"/>
<ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="DesignerItemTemplateLeftRight" TargetType="ContentControl">
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<s:MoveThumbLeftRight Template="{StaticResource MoveThumbTemplateLeftRight}" Cursor="SizeAll"/>
<ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="DesignerItemTemplateLine" TargetType="ContentControl">
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<s:MoveLine Template="{StaticResource MoveLineTemplate}" Cursor="SizeAll"/>
<ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
</Grid>
</ControlTemplate>
</Window.Resources>
<Canvas>
<ContentControl Width="600"
Height="5"
Canvas.Top="250"
Canvas.Left="80"
Template="{StaticResource DesignerItemTemplate}">
<Rectangle Fill="Blue"
IsHitTestVisible="False"/>
</ContentControl>
<ContentControl Width="5"
Height="400"
Canvas.Top="80"
Canvas.Left="350"
Template="{StaticResource DesignerItemTemplateLeftRight}">
<Rectangle Fill="Blue"
IsHitTestVisible="False"/>
</ContentControl>
<ContentControl Width="5" Height="400" Canvas.Top="80" Canvas.Left="350"
Template="{StaticResource DesignerItemTemplateLine}">
<Rectangle Fill="Blue" IsHitTestVisible="False" Name="mRect">
<Rectangle.RenderTransform>
<TransformGroup>
<RotateTransform Angle="45"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</ContentControl>
</Canvas>
Now the C# code
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows;
namespace DiagramDesigner
{
public class MoveThumbUpDwon : Thumb
{
public MoveThumbUpDwon()
{
DragDelta += new DragDeltaEventHandler(this.MoveThumb_DragDelta);
}
private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
Control designerItem = this.DataContext as Control;
if (designerItem != null)
{
double left = Canvas.GetLeft(designerItem);
double top = Canvas.GetTop(designerItem);
//Canvas.SetLeft(designerItem, left + e.HorizontalChange);
Canvas.SetTop(designerItem, top + e.VerticalChange);
}
}
}
public class MoveThumbLeftRight : Thumb
{
public MoveThumbLeftRight()
{
DragDelta += new DragDeltaEventHandler(this.MoveThumb_DragDelta);
}
private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
Control designerItem = this.DataContext as Control;
if (designerItem != null)
{
double left = Canvas.GetLeft(designerItem);
double top = Canvas.GetTop(designerItem);
Canvas.SetLeft(designerItem, left + e.HorizontalChange);
//Canvas.SetTop(designerItem, top + e.VerticalChange);
}
}
}
public class MoveLine : Thumb
{
public MoveLine()
{
DragDelta += new DragDeltaEventHandler(this.MoveThumb_DragDelta);
}
private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
Control designerItem = this.DataContext as Control;
if (designerItem != null)
{
double left = Canvas.GetLeft(designerItem);
double top = Canvas.GetTop(designerItem);
// if (mRect != null)
Canvas.SetLeft(designerItem, left + e.HorizontalChange);
Canvas.SetTop(designerItem, top + e.HorizontalChange);
//Canvas.SetTop(designerItem, top + e.VerticalChange);
}
}
}
}
The reference is in the code-behind, but it seems to be in a different class than the xaml:
<Window x:Class="DiagramDesigner.Window1"> vs public class MoveLine : Thumb
They must be the same class if you are going to reference xaml-defined elements from the code-behind.
It looks like your window's Canvas contains a number of ContentControls. One of these is of interest and contains a grid which in turn contains firstly an instance of your MoveLine class, and secondly a ContentPresenter containing a Rectangle.
So your visual tree looks roughly like this:
Window1
Canvas
...
ContentControl
Grid
MoveLine
ContentPresenter
Rectangle (mRect)
You're trying to handle an event in MoveLine and modify the ContentPresenter's Rectangle, mRect. You can refer to mRect only in the context of Window1.
The problem is that as far as WPF is concerned the MoveLine class could appear anywhere, and so naturally it has no idea what mRect might mean to any particular MoveLine instance. As it happens we know that mRect is the child Rectangle of a sibling ContentPresenter which shares a parent with an instance of MoveLine.
If you're absolutely sure that MoveLine will only ever be used here, you could use System.Windows.Media.VisualTreeHelper's GetParent(), GetChildrenCount() and GetChild() methods. You need to go "up" one level from MoveLine, across one, and then down one. You could also go up one level and then search for descendants with the name "mName". See How can I find WPF controls by name or type?. This isn't a very sane approach though.
I'd be more tempted to put the handling code onto the canvas, or at least into its ContentControls, since they're the ones being affected. I would add a RoutedEvent called, say, ThumbMoved, to MoveLine. RoutedEvents can "bubble up" to be handled by ancestral controls. You can then add a handler for this event to the ContentControl containing your Rectangle, and it can then use mName to adjust the rectangle. See How to: Create a Custom Routed Event and How to: Handle a Routed Event. Very roughly:
class MoveLine : Thumb
{
public static readonly RoutedEvent ThumbMovedEvent =
EventManager.RegisterRoutedEvent("ThumbMoved",
RoutingStrategy.Bubble,
typeof(DragDeltaRoutedEventHandler),
typeof(MoveLine));
public event DragDeltaRoutedEventHandler ThumbMoved
{
add { AddHandler(ThumbMovedEvent, value); }
remove { RemoveHandler(ThumbMovedEvent, value); }
}
void RaiseThumbMoved(DragDeltaEventArgs e)
{
DragDeltaRoutedEventArgs newEventArgs =
new DragDeltaRoutedEventArgs(ThumbMovedEvent, e);
RaiseEvent(newEventArgs);
}
private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
RaiseThumbMoved(e);
...
}
(Where DragDeltaRoutedEventArgs is a class derived from RoutedEventArgs which provides the same deltas as DragDeltaEventArgs.) And then in your window's XAML:
...
<ContentControl Width="5" Height="400" Canvas.Top="80" Canvas.Left="350"
Template="{StaticResource DesignerItemTemplateLine}"
MoveLine.ThumbMoved="MoveLine_ThumbMoved">
<Rectangle Fill="Blue" IsHitTestVisible="False" Name="mRect">
...
And code behind:
namespace DiagramDesigner
{
public partial class Window1 : Window
{
private void MoveLine_ThumbMoved(object sender, DragDeltaRoutedEventArgs e)
{
mRect.Foo = "Bar"; // mRect is recognised by Window1.
}
...

Resources