I am using MahApps and MVVM Light. And I want to make DropDownButton opens on mouse enter. And hide it when mouse cursor leaves button and opened menu. For code simplification, I don't write code with EventToCommand. I just write code behind
XAML
<controls:DropDownButton x:Name="ddbVolume" Width="{Binding Path=ActualHeight, RelativeSource={RelativeSource Self}}"
ItemsSource="{Binding AudioControls}"
Icon="{DynamicResource appbar_settings}" BorderThickness="0"
ArrowVisibility="Collapsed"
Loaded="OnDropDownButtonLoaded" MouseEnter="OnDropDownButtonMouseEnter">
</controls:DropDownButton>
and .cs
private void OnDropDownButtonMouseEnter(object sender, MouseEventArgs e)
{
var dropDownButton = sender as DropDownButton;
if (dropDownButton != null && !dropDownButton.IsExpanded)
{
dropDownButton.IsExpanded = true;
}
}
private void OnDropDownButtonLoaded(object sender, RoutedEventArgs e)
{
var dropDownButton = sender as DropDownButton;
if (dropDownButton != null)
{
var template = dropDownButton.Template;
var menu = (ContextMenu)template.FindName("PART_Menu", dropDownButton);
menu.MouseLeave += (o, args) =>
{
if (dropDownButton.IsExpanded && !dropDownButton.IsMouseOver && !menu.IsMouseOver)
{
dropDownButton.IsExpanded = false;
}
};
menu.PreviewMouseMove += (o, args) =>
{
if (!dropDownButton.IsExpanded)
{
return;
}
var x = args.GetPosition(menu).X;
var y = args.GetPosition(menu).Y;
if (x < 0 | y < 0 | x > menu.ActualWidth | y > menu.ActualHeight)
{
menu.ReleaseMouseCapture();
}
};
}
else
{
this._logger.Debug($"Error loading DropDownButton");
}
But it does not work. The DropDownButton is only flicker on mouse over. Please, give me a proper solution, or any usefull advice to solve this problem.
If the menu is appearing at all then your opening logic is good, but then it disappears, meaning that your own code is somehow closing it.
Stick a breakpoint on the line where you set dropDownButton.IsExpanded = false, and you'll see that's it's being called I'm sure. You can then use the debugger to see why it's been invoked and fix the problem in your xaml that's causing the system to think that your mouse has left the menu.
Maybe, you should subscribe the MouseLeave Event. And you could fix your Actions.
I have made a solution. And it works as i expect. The root of the problem was, that DropDownButton uses ContextMenu to show list items. And this control is based on Popup, which uses his own window. And MouseLeave fired not at time, when mouse coursor was not over it, but when it's lost focus.
XAML
<controls:DropDownButton x:Name="ddbVolume" Width="{Binding Path=ActualHeight, RelativeSource={RelativeSource Self}}"
ItemsSource="{Binding AudioControls}"
Icon="{DynamicResource appbar_settings}" BorderThickness="0"
ArrowVisibility="Collapsed">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<command:EventToCommand Command="{Binding Source={x:Static commands:CommonCommands.DropDownButtonLoadedCommand}}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseEnter">
<command:EventToCommand Command="{Binding Source={x:Static commands:CommonCommands.DropDownButtonMouseEnterCommand}}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</controls:DropDownButton>
And a ViewModel code (I know it's not a VM, but it works the same way)
In static class I define commands that can be used anywhere in my application.
public static class CommonCommands
{
private static ICommand dropDownButtonLoadedCommand;
private static ICommand dropDownButtonMouseEnterCommand;
public static ICommand DropDownButtonLoadedCommand => dropDownButtonLoadedCommand;
public static ICommand DropDownButtonMouseEnterCommand => dropDownButtonMouseEnterCommand;
static CommonCommands()
{
dropDownButtonLoadedCommand = new RelayCommand<RoutedEventArgs>(DropDownButtonLoaded, x => true);
dropDownButtonMouseEnterCommand = new RelayCommand<MouseEventArgs>(DropDownButtonMouseEnter, x => true);
}
private static void DropDownButtonLoaded(RoutedEventArgs args)
{
var dropDownButton = args.Source as DropDownButton;
if (dropDownButton != null)
{
var template = dropDownButton.Template;
var menu = (ContextMenu)template.FindName("PART_Menu", dropDownButton);
var button = (Button)template.FindName("PART_Button", dropDownButton);
menu.MouseLeave += (o, e) =>
{
if (dropDownButton.IsExpanded && !dropDownButton.IsMouseOver && !menu.IsMouseOver)
{
dropDownButton.IsExpanded = false;
}
};
menu.PreviewMouseMove += (o, e) =>
{
if (!dropDownButton.IsExpanded || !menu.IsOpen)
{
return;
}
var x = e.GetPosition(menu).X;
var y = e.GetPosition(menu).Y;
if (x < 0 | y < -button.ActualHeight | x > menu.ActualWidth | y > menu.ActualHeight)
{
menu.ReleaseMouseCapture();
}
};
}
}
private static void DropDownButtonMouseEnter(MouseEventArgs args)
{
var dropDownButton = args.Source as DropDownButton;
if (dropDownButton != null && !dropDownButton.IsExpanded)
{
dropDownButton.IsExpanded = true;
}
}
}
I know there are some little defects. For example, "expression y < -button.ActualHeight" is not good at all. the proper way is to use button.IsMouseOver in MouseLeave event.
Related
I have a drag and drop PopupBox in MaterialDesign. When drag and drop is done, the gui freezes and the data that is constantly renewed is not refreshed. How can I solve this Problem? Is it wrong to run in a separate thread? Is there just an overlooked point? Or is it all wrong?
my MouseUp Code
private void PortableButton_MouseUp(object sender, MouseButtonEventArgs e)
{
ThreadPool.QueueUserWorkItem(state => {
PortableButton.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() =>
{
e.MouseDevice.Capture(null);
}));
});
}
MouseMove Code
private void PortableButton_MouseMove(object sender, MouseEventArgs e)
{
ThreadPool.QueueUserWorkItem(state => {
var ActuelHeight = MainBorder.ActualHeight;
var ActuelWidth = MainBorder.ActualWidth;
PortableButton.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() =>
{
int _tempX = Convert.ToInt32(e.GetPosition(this).X);
int _tempY = Convert.ToInt32(e.GetPosition(this).Y);
if (_tempX < ActuelWidth && _tempX > 0 && _tempY < ActuelHeight && _tempY > 0)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
e.MouseDevice.Capture(PortableButton);
System.Windows.Thickness _margin = new System.Windows.Thickness();
_margin = MainGrid.Margin;
if (m_MouseX < _tempX)
{
_margin.Left += (_tempX - m_MouseX);
_margin.Right -= (_tempX - m_MouseX);
}
else
{
_margin.Left -= (m_MouseX - _tempX);
_margin.Right -= (_tempX - m_MouseX);
}
if (m_MouseY < _tempY)
{
_margin.Top += (_tempY - m_MouseY);
_margin.Bottom -= (_tempY - m_MouseY);
}
else
{
_margin.Top -= (m_MouseY - _tempY);
_margin.Bottom -= (_tempY - m_MouseY);
}
MainGrid.Margin = _margin;
m_MouseX = _tempX;
m_MouseY = _tempY;
}
}
}));
});
}
MouseLefButtonUp Code
double m_MouseX;
double m_MouseY;
private void PortableButton_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
ThreadPool.QueueUserWorkItem(state => {
this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() =>
{
m_MouseX = e.GetPosition(this).X;
m_MouseY = e.GetPosition(this).Y;
}));
});
}
Xaml Code
<Grid Name="MainGrid" Margin="96,308,156,0" VerticalAlignment="Top" Height="65" >
<materialDesign:PopupBox MouseUp="PortableButton_MouseUp" MouseLeftButtonUp="PortableButton_MouseLeftButtonUp" MouseMove="PortableButton_MouseMove" Name="PortableButton" Style="{StaticResource MaterialDesignMultiFloatingActionPopupBox}"
PlacementMode="BottomAndAlignCentres" ToolTipService.Placement="Right"
materialDesign:ShadowAssist.ShadowDepth="Depth3"
ToolTip="MenĂ¼">
<StackPanel>
<Button Name="SystemButton" ToolTip="System" PreviewMouseLeftButtonDown="SystemButton_PreviewMouseLeftButtonDown">
<Image Source="/Images/Icons/systemIcon.png" Width="45" ></Image>
</Button>
<Button Name="TestButton" ToolTip="Test" PreviewMouseLeftButtonDown ="TestButton_PreviewMouseLeftButtonDown">
<Image Source="/Images/Icons/testIcon.png" Width="30"></Image>
</Button>
<Button Name="PosButton" ToolTip="Pos" PreviewMouseLeftButtonDown ="PosClick">
<Image Source="/Images/Icons/positionIcon.png" Width="30"></Image>
</Button>
</StackPanel>
</materialDesign:PopupBox>
</Grid>
I add xaml code. There is constantly refreshing data on the screen. I get these with a special protocol with serialPort. Since it will be a very long code, it can be tried with the for loop that counts in the timer.
like some other people here i have a ListView (updated via binding in a GridView).
I want to keep the last inserted Item in the View. So i tried
LView.ScrollIntoView(LView.Items[LView.Items.Count - 1]);
This is working almost fine. Altough the first item which would have to be scrolled into view is only shown like 80% of its whole row (depending on how high i define the whole ListView, i almost got 100%).
The real problem is that the following items which should get scrolled into view are not shown. It is also noticable at the Scrollbar itself which is not at the bottom.
Last Item is not shown
Here is the code of my MainWindow.
public partial class MainWindow : Window
{
private InterfaceCtrl ICtrl;
private ListView LView;
public MainWindow()
{
InitializeComponent();
this.ICtrl = new InterfaceCtrl();
this.ICtrl.ProgressCollection.CollectionChanged += this.CollectionChanged;
Grid MainGrid = new Grid();
this.Content = MainGrid;
GridView gv = new GridView();
Binding StartTimeStampBinding = new Binding() { Path = new PropertyPath("StartTS"), Mode = BindingMode.OneWay, StringFormat = "dd.MM.yyyy - HH:mm:ss.fff" };
GridViewColumn gvTCStartTS = new GridViewColumn() { Header = "Time", Width = 150.00, DisplayMemberBinding = StartTimeStampBinding };
gv.Columns.Add(gvTCStartTS);
LView = new ListView() { Height = 192, Width = 250, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, View = gv, ItemsSource = this.ICtrl.ProgressCollection };
MainGrid.Children.Add(LView);
ICtrl.StartMyThread();
}
private void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Application.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new System.Action(delegate ()
{
if (LView != null && LView.Items.Count > 0)
{
LView.UpdateLayout();
//LView.ScrollIntoView(LView.Items[LView.Items.Count - 1]);
LView.SelectedIndex = LView.Items.Count;
LView.ScrollIntoView(LView.SelectedItem);
}
}));
}
}
Thank you.
EDIT:
It seemed to be a timing problem, although all the wanted data was in the LView at the right time i tried a workaround with a Textbox bound to the Timestamp.
TextBox tb = new TextBox(); // { Width = 250, Height = 28, Margin= new Thickness(10,100,1,0)};
tb.SetBinding( TextBox.TextProperty , new Binding("LastMsgTimestamp") { Source = this.ICtrl, Mode = BindingMode.OneWay, StringFormat = "dd.MM.yyyy - HH:mm:ss.fff" });
tb.TextChanged += this.UpdateScrollbar;
tb.Visibility = Visibility.Hidden;
It seems to me like there is a timing issue within the binding to the LView and the fired Event of the ObservableCollection. This also includes the PropertyChanged of the ObservableCollection.
I tried the events TargetUpdated and SoruceUpdated directly within LView but those didn't came up at all.
You could try to call any of the ScrollToBottom() or ScrollToVerticalOffset() methods of the ListView's internal ScrollViewer element:
private void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Application.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new System.Action(delegate ()
{
if (LView != null && LView.Items.Count > 0)
{
LView.UpdateLayout();
ScrollViewer sv = GetChildOfType<ScrollViewer>(LView);
if (sv != null)
sv.ScrollToBottom();
LView.SelectedIndex = LView.Items.Count;
LView.ScrollIntoView(LView.SelectedItem);
}
}));
}
private static T GetChildOfType<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj == null)
return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null)
return result;
}
return null;
}
I have made the following sample. You could try to call ScrollToBottom in inner ScrollViewer as #mm8 points out. Nevertheless when saw the answer I was already making my sample, so here it is:
Codebehind
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ListViewScroll
{
public partial class MainWindow : Window
{
public ObservableCollection<string> Names { get; set; }
public MainWindow()
{
InitializeComponent();
Names = new ObservableCollection<string>();
ListView.ItemsSource = Names;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Names.Add("Some Name" + ++someInt);
// Get the border of the listview (first child of a listview)
var border = VisualTreeHelper.GetChild(ListView, 0) as Decorator;
// Get scrollviewer
var scrollViewer = border.Child as ScrollViewer;
scrollViewer.ScrollToBottom();
}
private static int someInt;
}
}
XAML
<Window x:Class="ListViewScroll.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListView Grid.Row="0" Name="ListView"/>
<Button Content="Add" FontSize="20" Grid.Row="1"
Click="ButtonBase_OnClick"/>
</Grid>
</Window>
In this case I am handling the scrolling in the button click event but you may change this to fit your requirements
It works, I have tested.
Hope this helps
I want this to be good question, so I'll write in details what I would like to achieve, what I've found on the internet and I show what I've done so far and what I've tried.
I need to add drag and drop functionality to my application. I have Images (basically controls) that I want to drag to items of listbox.
Here is sample UI:
And here is usage I have now:
As You can see I'm able to drag one of four images and drop it over listbox item.
If I move image over correct target (listbox image) image near cursor disappears and everything works fine, but when I don't drop image on list item (I release mouse) that image stays on screen.
I've based my solution on answers to this question, and I'm unable to remove that unwanted window (image near cursor)
My XAML looks like this:
<Window x:Class="DragDrop.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Drag'n'Drop" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<ListBox HorizontalAlignment="Right" HorizontalContentAlignment="Stretch" Height="300" Margin="0,10,10,0" VerticalAlignment="Top" Width="234" ItemsSource="{Binding People}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" AllowDrop="True" PreviewDrop="UIElement_OnPreviewDrop">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<ProgressBar Height="20" Value="{Binding Points}" Margin="0,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Image HorizontalAlignment="Left" Height="72" Margin="10,10,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-60000-icon.png" RenderTransformOrigin="0.5,0.5"
PreviewMouseLeftButtonDown="OnMouseTouchDown"
PreviewTouchDown="OnMouseTouchDown"
PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="10"/>
<Image HorizontalAlignment="Left" Height="72" Margin="87,10,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-700000-icon.png" RenderTransformOrigin="0.5,0.5"
PreviewMouseLeftButtonDown="OnMouseTouchDown"
PreviewTouchDown="OnMouseTouchDown"
PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="20"/>
<Image HorizontalAlignment="Left" Height="72" Margin="10,87,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-7000-icon.png" RenderTransformOrigin="0.5,0.5"
PreviewMouseLeftButtonDown="OnMouseTouchDown"
PreviewTouchDown="OnMouseTouchDown"
PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="30"/>
<Image HorizontalAlignment="Left" Height="72" Margin="87,87,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-700-icon.png" RenderTransformOrigin="0.5,0.5"
PreviewMouseLeftButtonDown="OnMouseTouchDown"
PreviewTouchDown="OnMouseTouchDown"
PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="40"/>
</Grid>
</Window>
And code behind:
public partial class MainWindow
{
private readonly ObservableCollection<Person> _people = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get { return _people; }
}
public MainWindow()
{
InitializeComponent();
_people.Add(new Person() {Name = "Person1", Points = 10});
_people.Add(new Person() {Name = "Person2", Points = 0});
_people.Add(new Person() {Name = "Person3", Points = 40});
}
private void OnMouseTouchDown(object sender, InputEventArgs e)
{
var item = sender as Image;
if (item == null) return;
var draggedItem = item;
var points = Convert.ToInt32(draggedItem.Tag);
CreateDragDropWindow(draggedItem);
System.Windows.DragDrop.DoDragDrop(draggedItem, points, DragDropEffects.Move);
}
private Window _dragdropWindow;
private void CreateDragDropWindow(Visual dragElement)
{
_dragdropWindow = new Window
{
WindowStyle = WindowStyle.None,
AllowsTransparency = true,
AllowDrop = false,
Background = null,
IsHitTestVisible = false,
SizeToContent = SizeToContent.WidthAndHeight,
Topmost = true,
ShowInTaskbar = false
};
Rectangle r = new Rectangle
{
Width = ((FrameworkElement) dragElement).ActualWidth/2,
Height = ((FrameworkElement) dragElement).ActualHeight/2,
Fill = new VisualBrush(dragElement)
};
_dragdropWindow.Content = r;
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
_dragdropWindow.Left = w32Mouse.X;
_dragdropWindow.Top = w32Mouse.Y;
_dragdropWindow.Show();
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
private void UIElement_OnPreviewGiveFeedback(object sender, GiveFeedbackEventArgs e)
{
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
_dragdropWindow.Left = w32Mouse.X;
_dragdropWindow.Top = w32Mouse.Y;
}
private void UIElement_OnPreviewDrop(object sender, DragEventArgs e)
{
//var droppedData = e.Data.GetData(typeof(Image)) as Image;
var droppedData = (Int32) e.Data.GetData(typeof (Int32));
var stackPanel = sender as StackPanel;
if (stackPanel != null)
{
var student = stackPanel.DataContext as Person;
//int targetIndex = _people.IndexOf(student);
if (student != null) student.Points += droppedData;
}
if (_dragdropWindow != null)
{
_dragdropWindow.Close();
_dragdropWindow = null;
}
}
}
public class Person : INotifyPropertyChanged
{
private string _name;
private int _points;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
public int Points
{
get { return _points; }
set
{
if (value == _points) return;
_points = value;
if (_points >= 100)
{
_points -= 100;
Debug.WriteLine("100!");
}
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I found over the internet that I can use class that extends Adorner and I found some examples:
http://nonocast.cn/adorner-in-wpf-part-5-drag-and-drop/ - (WaybackMachine archive link)
http://www.zagstudio.com/blog/488#.VfiMSBHtmko
http://www.infragistics.com/community/blogs/alex_fidanov/archive/2009/07/28/drag-amp-drop-with-datapresenter-family-controls.aspx
https://github.com/punker76/gong-wpf-dragdrop
but all of them show how to drag items from collections (ItemsControls). Third link was promising, but I wasn't able to adopt it to my needs.
So my questions are:
How can I hide that small image window in my example when I cancel drag (MouseUp or incorrect drag target)
Show I use Adorner and how can I use it in my code? I need to show it when I start drag and hide when I drop Image correctly or I cancel drag or drop target is incorrect
I'm starting with WPF so please try to understand my frustration - I've spend last two evenings and night trying to get this working.
1) Modify your OnMouseTouchDown handler to include assigning ContinueDragHandler to dragged item before starting the drag, like this
private void OnMouseTouchDown(object sender, InputEventArgs e)
{
var item = sender as FrameworkElement;
if (item == null) return;
var draggedItem = item;
var points = Convert.ToInt32(draggedItem.Tag);
CreateDragDropWindow(draggedItem);
System.Windows.DragDrop.AddQueryContinueDragHandler(draggedItem, DragContrinueHandler);
System.Windows.DragDrop.DoDragDrop(draggedItem, points, DragDropEffects.Move);
}
And the handler itself:
public void DragContrinueHandler(object sender, QueryContinueDragEventArgs e)
{
if (e.Action == DragAction.Continue && e.KeyStates != DragDropKeyStates.LeftMouseButton)
{
_dragdropWindow.Close();
}
}
2) I believe that creating a new window to display image next to a cursor is a dirty dirty hack. There are plenty of various articles around about using adorners with drag'n'drop. Althought your approach works and doesn't require a lot of code. Adorners do, on the other hand. I think you should create another question, if you fail following certain tutorial, with code examples and what steps you took
Being new to WPF and MVVM I searched everywhere to find a good answer to my problem. I'm creating a cropping application but I'm trying to migrate the code behind codes to a view model. I was able to bind my mouse button event by using blends interactivity triggers code is below:
<Grid x:Name="GridLoadedImage" HorizontalAlignment="Left" VerticalAlignment="Top">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding MouseLeftButtonDownCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding MouseLeftButtonUpCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseMove">
<i:InvokeCommandAction Command="{Binding MouseMoveCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid.LayoutTransform>
<ScaleTransform ScaleX="{Binding ElementName=slider1, Path=Value}" ScaleY="{Binding ElementName=slider1, Path=Value}"/>
</Grid.LayoutTransform>
<Image x:Name="LoadedImage" Margin="10" Source="{Binding ImagePath}"/>
<Canvas x:Name="BackPanel" Margin="10">
<Rectangle x:Name="selectionRectangle" Stroke="LightBlue" Fill="#220000FF" Visibility="Collapsed"/>
</Canvas>
</Grid>
Now my dilema is how to migrate the actual code i used from my code behind which is shown below:
private void LoadedImage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (isDragging == false)
{
anchorPoint.X = e.GetPosition(BackPanel).X;
anchorPoint.Y = e.GetPosition(BackPanel).Y;
Canvas.SetZIndex(selectionRectangle, BackPanel.Children.Count);
isDragging = true;
BackPanel.Cursor = Cursors.Cross;
}
}
private void LoadedImage_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
double x = e.GetPosition(BackPanel).X;
double y = e.GetPosition(BackPanel).Y;
selectionRectangle.SetValue(Canvas.LeftProperty, Math.Min(x, anchorPoint.X));
selectionRectangle.SetValue(Canvas.TopProperty, Math.Min(y, anchorPoint.Y));
selectionRectangle.Width = Math.Abs(x - anchorPoint.X);
selectionRectangle.Height = Math.Abs(y - anchorPoint.Y);
if (selectionRectangle.Visibility != Visibility.Visible)
selectionRectangle.Visibility = Visibility.Visible;
}
}
private void LoadedImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (isDragging)
{
isDragging = false;
if (selectionRectangle.Width > 0)
{
Crop.IsEnabled = true;
Cut.IsEnabled = true;
BackPanel.Cursor = Cursors.Arrow;
}
}
}
As you can see I would need to be able to access the x and y coordinates as well as the width and height of the rectangle (which is named selection rectangle). I was thinking of creating the canvas and rectangle inside my viewmodel but that would be against the mvvm structure. I read that I could use attached properties but not familiar with it. What is the best possible way of handling this with respect to MVVM pattern. Currently I'm reading a book by Adan Nathan WPF unleashed 4 which is a great book for beginners like me but i cant seem to find anything that relates to my problem. Thanks for any help
I do have a view model code for my mouse event:
#region MouseLeftButtonDown
private bool isDragging = false;
private Point anchorPoint = new Point();
private ICommand _mouseLeftButtonDownCommand;
public ICommand MouseLeftButtonDownCommand
{
get
{
if (_mouseLeftButtonDownCommand == null)
{
_mouseLeftButtonDownCommand = new RelayCommand(param => MouseLeftButtonDown());
}
return _mouseLeftButtonDownCommand;
}
}
public void MouseLeftButtonDown()
{
if (isDragging == false)
{
MessageBox.Show("THis is Mouse Down");
//anchorPoint.X = e.GetPosition(BackPanel).X;
//anchorPoint.Y = e.GetPosition(BackPanel).Y;
isDragging = true;
}
}
#endregion
#region MouseLeftButtonUp
private ICommand _mouseLeftButtonUpCommand;
public ICommand MouseLeftButtonUpCommand
{
get
{
if (_mouseLeftButtonUpCommand == null)
{
_mouseLeftButtonUpCommand = new RelayCommand(param => MouseLeftButtonUp((MouseButtonEventArgs)param));
}
return _mouseLeftButtonUpCommand;
}
}
public void MouseLeftButtonUp(MouseButtonEventArgs e)
{
if (isDragging)
{
MessageBox.Show(e.Source.ToString());
isDragging = false;
//if (selectionRectangle.Width > 0)
//{
// Crop.IsEnabled = true;
// Cut.IsEnabled = true;
// BackPanel.Cursor = Cursors.Arrow;
//}
}
}
#endregion
#region MouseMove
private ICommand _mouseMoveCommand;
public ICommand MouseMoveCommand
{
get
{
if (_mouseMoveCommand == null)
{
_mouseMoveCommand = new RelayCommand(param => MouseMove());
}
return _mouseMoveCommand;
}
}
public void MouseMove()
{
if (isDragging)
{
//MessageBox.Show("THis is Mouse Move");
//double x = e.GetPosition(BackPanel).X;
//double y = e.GetPosition(BackPanel).Y;
//selectionRectangle.SetValue(Canvas.LeftProperty, Math.Min(x, anchorPoint.X));
//selectionRectangle.SetValue(Canvas.TopProperty, Math.Min(y, anchorPoint.Y));
//selectionRectangle.Width = Math.Abs(x - anchorPoint.X);
//selectionRectangle.Height = Math.Abs(y - anchorPoint.Y);
//if (selectionRectangle.Visibility != Visibility.Visible)
// selectionRectangle.Visibility = Visibility.Visible;
}
}
#endregion
I just commented the actual code and replace it with message boxes just to test if my trigger work which it does. This 3 functions once I figure out how to make it work would draw the cropping rectangle on top of the imaged being cropped. I do have a crop button the will be enabled once the rectangle is completed and this button will be bound to another function that would be the actual cropping function.
That's more simple than you may have thought.
What you are doing is an UserControl which userdefined behaviour. So rather than putting that XAML into your Page/View, you implement your own Control which derives from UserControl and implement your code as you have in your code-behind.
Since you are making a custom control, you don't have to follow MVVM for it. In fact, MVVM patter for user controls is discouraged. In your custom control you define may define a Dependency Property which holds an Object of type "SelectionRect" (you shouldn't be using Rect as it's a struct and it doesn't work well with databinding, as it creates a new copy of it each time it changes).
public class CropControl : UserControl
{
public Rect Selection
{
get { return (Rect)GetValue(SelectionProperty); }
set { SetValue(SelectionProperty, value); }
}
public static readonly DependencyProperty SelectionProperty =
DependencyProperty.Register("Selection", typeof(Rect), typeof(CropControl), new PropertyMetadata(default(Rect)));
// this is used, to react on changes from ViewModel. If you assign a
// new Rect in your ViewModel you will have to redraw your Rect here
private static void OnSelectionChanged(System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e)
{
Rect newRect = (Rect)e.NewValue;
Rectangle selectionRectangle = d as Rectangle;
if(selectionRectangle!=null)
return;
selectionRectangle.SetValue(Canvas.LeftProperty, newRect.X);
selectionRectangle.SetValue(Canvas.TopProperty, newRect.Y);
selectionRectangle.Width = newRect.Width;
selectionRectangle.Height = newRect.Height;
}
private void LoadedImage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (isDragging == false)
{
anchorPoint.X = e.GetPosition(BackPanel).X;
anchorPoint.Y = e.GetPosition(BackPanel).Y;
Canvas.SetZIndex(selectionRectangle, BackPanel.Children.Count);
isDragging = true;
BackPanel.Cursor = Cursors.Cross;
}
}
private void LoadedImage_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
double x = e.GetPosition(BackPanel).X;
double y = e.GetPosition(BackPanel).Y;
selectionRectangle.SetValue(Canvas.LeftProperty, Math.Min(x, anchorPoint.X));
selectionRectangle.SetValue(Canvas.TopProperty, Math.Min(y, anchorPoint.Y));
selectionRectangle.Width = Math.Abs(x - anchorPoint.X);
selectionRectangle.Height = Math.Abs(y - anchorPoint.Y);
if (selectionRectangle.Visibility != Visibility.Visible)
selectionRectangle.Visibility = Visibility.Visible;
}
}
private void LoadedImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (isDragging)
{
isDragging = false;
if (selectionRectangle.Width > 0)
{
Crop.IsEnabled = true;
Cut.IsEnabled = true;
BackPanel.Cursor = Cursors.Arrow;
}
// Set the Selection to the new rect, when the mouse button has been released
Selection = new Rect(
selectionRectangle.GetValue(Canvas.LeftProperty),
selectionRectangle.GetValue(Canvas.TopProperty),
selectionRectangle.Width,
selectionRectangle.Height);
}
}
}
Notice the only changes were to add Selection = new Rect(...) and the Dependency Property.
Then you can bind it in XAML.
<my:CropControl Selection="{Binding Selection,Mode=TwoWay}"/>
Update:
Your ViewModel would look something like
public class MyViewModel : ViewModel
{
private Rect selection;
public Rect Selection
{
get
{
return selection;
}
set
{
selection = value;
// Or whatever the name of your framework/implementation the method is called
OnPropertyChanged("Selection");
// Cause ICommands to reevaluate their CanExecute methods
CommandManager.InvalidateRequerySuggested();
}
}
private ICommand cropCommand;
public ICommand CropCommand {
get
{
if(cropCommand==null)
cropCommand = new RelayCommand(Crop, () => Selection.Width > 0); // only allow execution when Selection width > 0
return cropCommand;
}
}
public void Crop()
{
// Get a copy of the selection in case it changes during execution
Rect cropSelection = Selection;
// use it to crop your image
...
}
}
Drawing Selection = View Logic (So View)
Cropping with a Rect given by CropControl => Presentation/Business Logic (so ViewModel)
Doing so, allows you to reuse your CropControl in other applications. If you put your "selectionRect" drawing code into your ViewModel (which may be possible, but causes hard to read and maintain code), then you can't reuse it in other application, since your ViewModels are specific to your application.
Hope that helps.
MVVM means separating View from ViewModel. The example you give is typically a View only code.
Your example seems to be a kind of selection tool, I deduce you want to get the selected content back, or at least the cropping coordinates. So the best is to transform your code in a custom control exposing a Rect DependencyProperty for the crop coordinates, and in your view model, you should expose a Rect property holding the cropping rectangle coordinates, and then Bind it to your cropping control DepencyProperty.
The view is about interacting with visual aspects. The ViewModel is about holding and working with the data used by the view.
I would like to know how to disable ComboBox DropDown Button Programmatically. I had seen many similar subjects but all of these have a XAML solution.
By the way, if someone know how to disable all ComboBox control design and left visible only item template it can be helpful too.
UPDATE
its my XAML definition
<ComboBox Name="lang_ComboBox" SelectionChanged="LanguageSelection_ComboBox_SelectionChanged"/>
And there is how i use it:
String text = "dorf";
BitmapImage image = new BitmapImage(new Uri("http://img88.imageshack.us/img88/4351/butchermi4.png"));
lang_ComboBox.Width = 100;
lang_ComboBox.Height = 30;
Grid sp;
for (int i = 0; i < 5; i++)
{
ColumnDefinition gridCol1 = new ColumnDefinition();
gridCol1.Width = new GridLength(30.0);
ColumnDefinition gridCol2 = new ColumnDefinition();
gridCol2.Width = new GridLength(70.0);
sp = new Grid()
{
Width = 100,
Height = 30
};
Image im = new Image()
{
Source = image,
Width = 25,
Height = 25
};
Label la = new Label()
{
Content = text
};
sp.ColumnDefinitions.Add(gridCol1);
sp.ColumnDefinitions.Add(gridCol2);
Grid.SetColumn(im, 0);
Grid.SetColumn(la, 1);
sp.Children.Add(la);
sp.Children.Add(im);
lang_ComboBox.Items.Add(sp);
}
UPDATE 2
Hmmm I get it now, I use wrong word. It should be "Hide" control design and still can choose from a list. My bad sorry. But i know how i can solve it with Anatoliy Nokolaev's Code. To hide control design i use:
ToggleButton dropDownButton = GetFirstChildOfType<ToggleButton>(lang_ComboBox);
dropDownButton.Visibility = System.Windows.Visibility.Collapsed;
Unwanted behavior is now only that i cant show combobox dropdownmenu, but I'll invoke it programmatically by add on click event and should be good.
If there is any easiest way to do this tell me :).
To disable only the ToggleButton in ComboBox programmatically, you need to find this in the ComboBox control using VisualTreeHelper and assign a property IsEnabled to false, like this:
XAML
<Window x:Class="DisableComboBoxButton.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded">
<StackPanel>
<ComboBox Name="comboBox"
Width="100"
Height="25"
SelectedIndex="0">
<ComboBoxItem>Test1</ComboBoxItem>
<ComboBoxItem>Test2</ComboBoxItem>
<ComboBoxItem>Test3</ComboBoxItem>
</ComboBox>
<ComboBox Name="AllComboBoxDisabled"
Width="100"
Height="25"
IsEnabled="False"
SelectedIndex="0">
<ComboBoxItem>Test1</ComboBoxItem>
<ComboBoxItem>Test2</ComboBoxItem>
<ComboBoxItem>Test3</ComboBoxItem>
</ComboBox>
</StackPanel>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ToggleButton dropDownButton = GetFirstChildOfType<ToggleButton>(comboBox);
dropDownButton.IsEnabled = false;
}
public static T GetFirstChildOfType<T>(DependencyObject dependencyObject) where T : DependencyObject
{
if (dependencyObject == null)
{
return null;
}
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
{
var child = VisualTreeHelper.GetChild(dependencyObject, i);
var result = (child as T) ?? GetFirstChildOfType<T>(child);
if (result != null)
{
return result;
}
}
return null;
}
}
Output
Notes
Always use GetFirstChildOfType() function only when the control will be fully loaded, otherwise it will not find it and give null. In this case, I put this code in the event Window_Loaded which says that all the controls of the Window successfully load.
Edit: another version
Not to say that this version is easier to implement, but it would be more correct and a bit easier to use.
So, we need a template for your ComboBox, because it allows access to elements that are within the control. Just like that, the ToggleButton can not be accessed from both the code and of XAML.
We create attached dependency property that will serve the current ComboBox another property, such as which will give access to our button Visibility.
Our property Visibility:
public static class ButtonExt
{
public static readonly DependencyProperty VisibilityProperty;
public static void SetVisibility(DependencyObject DepObject, Visibility value)
{
DepObject.SetValue(VisibilityProperty, value);
}
public static Visibility GetVisibility(DependencyObject DepObject)
{
return (Visibility)DepObject.GetValue(VisibilityProperty);
}
static ButtonExt()
{
PropertyMetadata VisibiltyPropertyMetadata = new PropertyMetadata(Visibility.Collapsed);
VisibilityProperty = DependencyProperty.RegisterAttached("Visibility",
typeof(Visibility),
typeof(ButtonExt),
VisibiltyPropertyMetadata);
}
}
Setter property in ComboBox template (skip version, full version see in project in App.xaml file):
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid>
<ToggleButton Name="ToggleButton"
Template="{StaticResource ComboBoxToggleButton}"
IsChecked="{Binding Path=IsDropDownOpen,
Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
Visibility="{TemplateBinding PropertiesExtension:ButtonExt.Visibility}" // <------ Here
Grid.Column="2"
Focusable="False"
ClickMode="Press" />
Now, we are setting this property like this:
<ComboBox Name="comboBox"
Style="{StaticResource ComboBoxBaseStyle}"
PropertiesExtension:ButtonExt.Visibility="Visible"
Width="100"
Height="30"
SelectedIndex="0">
<ComboBoxItem>Test1</ComboBoxItem>
<ComboBoxItem>Test2</ComboBoxItem>
<ComboBoxItem>Test3</ComboBoxItem>
</ComboBox>
or in code-behind via Click event handlers:
private void HideButton_Click(object sender, RoutedEventArgs e)
{
ButtonExt.SetVisibility(comboBox, Visibility.Hidden);
}
private void ShowButton_Click(object sender, RoutedEventArgs e)
{
ButtonExt.SetVisibility(comboBox, Visibility.Visible);
}
Full version of example project is here.
Try with this
Dispatcher.BeginInvoke(new Action(() =>
{
ToggleButton dropDownButton = GetFirstChildOfType<ToggleButton>(cboMedicos);
if (dropDownButton != null)
{
dropDownButton.IsEnabled = false;
}
}), System.Windows.Threading.DispatcherPriority.Render);
public static T GetFirstChildOfType<T>(DependencyObject dependencyObject) where T : DependencyObject
{
if (dependencyObject == null)
{
return null;
}
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
{
var child = VisualTreeHelper.GetChild(dependencyObject, i);
var result = (child as T) ?? GetFirstChildOfType<T>(child);
if (result != null)
{
return result;
}
}
return null;
}