Problems using inheritance with drag and drop in WPF - wpf

I have a usercontrol that I want to implement a drag and drop interface on, here are the vital parts of the implementation, and this works fine:
XML-file to the usercontrol to be draggable:
<UserControl
...default xmlns...
MouseLeftButtonDown="Control_MouseLeftButtonDown">
...GUI-ELEMENTS in the control...
</UserControl>
Code behind:
public partial class DragableControl : UserControl
{
private void Control_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragDrop.DoDragDrop(this, this, DragDropEffects.Move);
}
}
XML-file to the usercontrol which will be able to accept a drag and drop operation:
<Usercontrol
...default xmlns...>
<Grid AllowDrop="True" Drop="Grid_Drop">
... GUI elements in the grid....
</Grid>
</Usercontrol>
Code behind:
public partial class DropClass: UserControl
{
private void Grid_Drop(object sender, DragEventArgs e)
{
var control = (DragableControl)e.Data.GetData(typeof(DragableControl));
if(control != null)
{
//do something
}
}
}
To be able to create different usercontrols which have drag and drop functionality, I creates a base class, BaseDragableUserControl, which at the moment contains nothing, but inherits from usercontrol.
Code:
public class BaseDragableUserControl: UserControl
{
}
I change my code (both xaml and code):
public partial class DragableControl : UserControl
I also changes the class for receiving to this:
public partial class DropClass: UserControl
{
private void Grid_Drop(object sender, DragEventArgs e)
{
var control =(BaseDragableUserControl)e.Data.GetData(typeof(BaseDragableUserControl));
if(control != null)
{
//do something
}
}
}
But the control variable is always Null. I guess that the getdata in the DragEventsArgs does not like inheritance. Is there a way to achieve this? To make it possible to use a base class for a drag and drop class?

Instead of passing this when you initiate the drag/drop, create one of the standard containers for that purpose. Specifically:
DragDrop.DoDragDrop(this, new DataObject("myFormat", this), DragDropEffects.Move);
and then now you know to expect a specific kind of data:
var control =(BaseDragableUserControl)e.Data.GetData("myFormat");

Related

Incomplete display of items in combobox wpf c#

I'm using wpf c# and Entity Framework
I have a DataGrid on that show data from database
when users click on datagrid that row will show items in ComboBox (Load on of columns in combobox)
but problem is combobox doesn't show Normal list
Code CS Behind :
DENAF1399Entities dbms = new DENAF1399Entities();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var qre = dbms.Database.SqlQuery<Q_View>("SELECT * FROM Q_View");
datagrid1.ItemsSource = qre.ToList();
}
private void datagrid1_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Q_View QVkala = datagrid1.SelectedItem as Q_View;
if (QVkala != null)
{
combobox1.ItemsSource = QVkala.NAMES;
}
}
I tried
-Change Fonts of combobox
-use new combobox
but didn't work
please help me
Edit during formation: It just became obvious to me what's going on. Q_View.NAMES is a string, and by setting combobox1.ItemsSource to that property, it's identifying the individual items as characters in the string (as string is an IEnumerable<char>).
If what you want in the combo box is what's in each of the columns of the selected item, then the way to do that is like this:
private void datagrid1_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Q_View QVkala = datagrid1.SelectedItem as Q_View;
if (QVkala != null)
{
object[] items = { QVkala.CODE, QVkala.NAME, QVkala.NAMES, QVkala.TOZIH } //etc whatever properties you want to project into this
combobox1.ItemsSource = items;
}
}
ORIGINAL WORK ON AN ANSWER
At first glance it looks like your data is transposed, but altogether it looks like you aren't using WPF or Entity Framework like you really could be using them. WPF was made for MVVM design and Entity Framework was made for treating tables like collections of objects. Not knowing much else about your application, here's how I'd get started:
First, I'd move basically everything except what's auto-generated out of MainWindow.xaml.cs, and start a new separate class. (Note: may have compiler errors as this is completely off the cuff)
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged; //MainWindow.xaml will hook into this
public ObservableCollection<Q_View> Q_Views { get; private set; }
private Q_View selectedQView;
public Q_View SelectedQView
{
get => selectedQView;
set
{
if(value != selectedQView)
{
selectedQView = value;
PropertyChanged?.Invoke("SelectedQView");
}
}
}
}
And then in MainWindow.xaml.cs, the only change from what's generated would be the constructor (there's another way to do this even without changing the code-behind but I'll not get into it here since I'm not as xaml-adept as I am with C#)
public class MainWindow : Window
{
public MainWindow()
{
DataContext = new MainWindowViewModel();
InitializeComponent(); //that's auto-generated
}
}
And finally, the xaml for your DataGrid. Edit it like this:
<DataGrid Name="QViewDataGrid" AutoGenerateColumns="False" ItemsSource="{Binding Q_Views}" SelectedItem="{Binding SelectedQView}">
<DataGrid.Columns>
<DataGridTextColumn Header="CODE" Binding="{Binding Path="CODE"}"> //and so forth with more columns
</DataGrid.Columns>
</DataGrid>
ComboBox will have a similar syntax for binding an ItemsSource and SelectedItem. Doing this enables you to avoid having event handlers and dealing with boiler plate for updating so many things.

Where is ExecWB?

If I use WPF syntax:
<WebBrowser Grid.Row="1" Grid.Column="1" x:Name="htmlView"></WebBrowser>
Or if I add a WindowsFormsHost:
<WindowsFormsHost x:Name="formsHost" Grid.Row="1" Grid.Column="1">
</WindowsFormsHost>
And in the CS file:
public partial class MainWindow : Window
{
System.Windows.Forms.WebBrowser htmlView= new System.Windows.Forms.WebBrowser();
public MainWindow()
{
InitializeComponent();
formsHost.Child = htmlView;
htmlView.Navigate("d:\\test.xml");
}
private void menuFilePageSetup_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Do do");
}
}
Either way, the ExecWB method is not exposed. So I can't port this code over from my MFC C++ CHtmlView derived class:
ExecWB(OLECMDID_PAGESETUP, OLECMDEXECOPT_DONTPROMPTUSER, NULL, NULL);
Am I missing something? At this point in time I can't implement my print preview / page setup / zoom functionality because I can't use ExecWB.
I have tried deriving my own class from the Winforms browser but it is still not listed.
Thank you.
I don't understand why in this question:
How do I programmatically change printer settings with the WebBrowser control?
It refers to ShowPrintDialog but even that is not listed.
Partial success! I don't know why, but now, when I use the hosted Winforms version, I can atleast see some of the methods:
public partial class MainWindow : Window
{
System.Windows.Forms.WebBrowser htmlView= new System.Windows.Forms.WebBrowser();
public MainWindow()
{
InitializeComponent();
formsHost.Child = htmlView;
htmlView.Navigate("d:\\test.xml");
}
private void menuFilePageSetup_Click(object sender, RoutedEventArgs e)
{
htmlView.ShowPageSetupDialog();
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
htmlView.ShowPrintPreviewDialog();
}
}
But ExecWb is still missing for managing things like zooming. I found this:
How do I print from the wpf WebBrowser available in .net 3.5 SP1?
One of the answers:
mshtml.IHTMLDocument2 doc = webBrowser.Document as mshtml.IHTMLDocument2;
doc.execCommand("Print", true, null);
But even if I add a reference to the mshtml library the compiler will not let me convert from HtmlDocument to IHtmlDocument2. Eventually I got this:
Clearing the selection in a webbrowser control
So now I know how to select all and copy to clipboard again (as long as I use the Winforms edition). However, having looked here:
https://msdn.microsoft.com/en-us/library/ms533049(v=vs.85).aspx
There seems to be no match for Optical Zoom:
ExecWB(OLECMDID_OPTICAL_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, &vZoom, NULL);
The basic answer for calling ExecWb is to do this:
Use the WinForms WebBrowser control
Then something like htmlView.Document.ExecCommand
Pass in commands like:
SelectAll
Copy
for clipboard items.
There are native methods for displaying the print dialogues etc..
Only thing that does not seem to be supported is:
ExecWB(OLECMDID_OPTICAL_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, &vZoom, NULL);

How to access ShowMessageAsync method of MetroWindow from ViewModel

I am using MahApps.metro WPF library with MVVM. I have a ViewModel from which I need to display a Dialog. The MetroWindow has ShowMessageAsync. But what is the proper way to access it from the ViewModel? As I understand I need a View instance but passing that into the ViewModel doesn't seem like a good approach.
Use following approach:
Take an Action<T> ShowMessageAsync in your ViewModel which you are binding with window.
Now create a behaviour for Window and use following code in behaviour
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Loaded += AssociatedObject_Loaded;
}
void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
if (this.AssociatedObject.DataContext is WindowViewModel)
{
WindowViewModel vm = this.AssociatedObject.DataContext as WindowViewModel;
vm.ShowMessageAsync = OnShowMessageAsync;
}
}
private void OnShowMessageAsync(T param)
{
//Write your logic to call ShowMessageAsync method.
}
Now in this way, from the ViewModel of your MainWindow you will have ability to open another child window.

How to receive the InkCanvas.StrokeCollected event in the view model

In using MVVM pattern, I have a custom inkcanvas with:
protected override void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e)
{
CustomStroke newStroke = new CustomStroke(e.Stroke.StylusPoints, e.Stroke.DrawingAttributes);
this.Strokes.Remove(e.Stroke);
this.Strokes.Add(newStroke);
InkCanvasStrokeCollectedEventArgs eNew = new InkCanvasStrokeCollectedEventArgs(newStroke);
// Raises the System.Windows.Controls.InkCanvas.StrokeCollected event.
base.OnStrokeCollected(eNew);
}
How do I get the view model to receive the InkCanvas.StrokeCollected event?
I can not bind the XAML to the strokes as the StrokeCollection.CollectionChanged event will be called three times by the custom inkcanvas.
Any help is appreciated.
Try this
public Window3()
{
InitializeComponent();
var vm=new ViewModel();
this.DataContext = vm;
canvas.StrokeCollected += vm.OnStrokeCollected;
}
ViewModel
public class ViewModel
{
public void OnStrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
{
}
}
Edit
if you want to do it without codebehind see the article EventTrigger
You simply bind it via XAML as you already did, which is the correct way to do it.
That you get 3 events, doesn't matter. Just handle the one you need.
For example, if you are only interested in the StrokeCollectedEvent, then just do
protected override void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e)
{
if(e.RoutedEvent != InkCanvas.StrokeCollectedEvent)
return;
// handle the event
}
For a full list of Events, consult the "Fields" Section of InkCanvas MSDN documentation. The fields ending with "Event" are RoutedEvent constants, which are passed in the InkCanvasStrokeCollectedEventArgs.

How to bind DataGridColumn.Visibility?

I have an issue similar to the following post:
Silverlight DataGridTextColumn Binding Visibility
I need to have a Column within a Silverlight DataGrid be visibile/collapsed based on a value within a ViewModel. To accomplish this I am attempting to Bind the Visibility property to a ViewModel. However I soon discovered that the Visibility property is not a DependencyProperty, therefore it cannot be bound.
To solve this, I attempted to subclass my own DataGridTextColumn. With this new class, I have created a DependencyProperty, which ultimately pushes the changes to the DataGridTextColumn.Visibility property. This works well, if I don't databind. The moment I databind to my new property, it fails, with a AG_E_PARSER_BAD_PROPERTY_VALUE exception.
public class MyDataGridTextColumn : DataGridTextColumn
{
#region public Visibility MyVisibility
public static readonly DependencyProperty MyVisibilityProperty =
DependencyProperty.Register("MyVisibility", typeof(Visibility), typeof(MyDataGridTextColumn), new PropertyMetadata(Visibility.Visible, OnMyVisibilityPropertyChanged));
private static void OnMyVisibilityPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var #this = d as MyDataGridTextColumn;
if (#this != null)
{
#this.OnMyVisibilityChanged((Visibility)e.OldValue, (Visibility)e.NewValue);
}
}
private void OnMyVisibilityChanged(Visibility oldValue, Visibility newValue)
{
Visibility = newValue;
}
public Visibility MyVisibility
{
get { return (Visibility)GetValue(MyVisibilityProperty); }
set { SetValue(MyVisibilityProperty, value); }
}
#endregion public Visibility MyVisibility
}
Here is a small snippet of the XAML.
<DataGrid ....>
<DataGrid.Columns>
<MyDataGridTextColumn Header="User Name"
Foreground="#FFFFFFFF"
Binding="{Binding User.UserName}"
MinWidth="150"
CanUserSort="True"
CanUserResize="False"
CanUserReorder="True"
MyVisibility="{Binding Converter={StaticResource BoolToVisibilityConverter}, Path=ShouldShowUser}"/>
<DataGridTextColumn .../>
</DataGrid.Columns>
</DataGrid>
A couple important facts.
The Converter is indeed defined above in the local resources.
The Converter is correct, it is used many other places in the solution.
If I replace the {Binding} syntax for the MyVisibility property with "Collapsed" the Column does in fact disappear.
If I create a new DependencyProperty (i.e. string Foo), and bind to it I receive the AG_E_PARSER_BAD_PROPERTY_VALUE exception too.
Does anybody have any ideas as to why this isn't working?
Here's the solution I've come up with using a little hack.
First, you need to inherit from DataGrid.
public class DataGridEx : DataGrid
{
public IEnumerable<string> HiddenColumns
{
get { return (IEnumerable<string>)GetValue(HiddenColumnsProperty); }
set { SetValue(HiddenColumnsProperty, value); }
}
public static readonly DependencyProperty HiddenColumnsProperty =
DependencyProperty.Register ("HiddenColumns",
typeof (IEnumerable<string>),
typeof (DataGridEx),
new PropertyMetadata (HiddenColumnsChanged));
private static void HiddenColumnsChanged(object sender,
DependencyPropertyChangedEventArgs args)
{
var dg = sender as DataGrid;
if (dg==null || args.NewValue == args.OldValue)
return;
var hiddenColumns = (IEnumerable<string>)args.NewValue;
foreach (var column in dg.Columns)
{
if (hiddenColumns.Contains ((string)column.GetValue (NameProperty)))
column.Visibility = Visibility.Collapsed;
else
column.Visibility = Visibility.Visible;
}
}
}
The DataGridEx class adds a new DP for hiding columns based on the x:Name of a DataGridColumn and its descendants.
To use in your XAML:
<my:DataGridEx x:Name="uiData"
DataContext="{Binding SomeDataContextFromTheVM}"
ItemsSource="{Binding Whatever}"
HiddenColumns="{Binding HiddenColumns}">
<sdk:DataGridTextColumn x:Name="uiDataCountOfItems">
Header="Count"
Binding={Binding CountOfItems}"
</sdk:DataGridTextColumn>
</my:DataGridEx>
You need to add these to your ViewModel or whatever data context you use.
private IEnumerable<string> _hiddenColumns;
public IEnumerable<string> HiddenColumns
{
get { return _hiddenColumns; }
private set
{
if (value == _hiddenColumns)
return;
_hiddenColumns = value;
PropertyChanged (this, new PropertyChangedEventArgs("HiddenColumns"));
}
}
public void SomeWhereInYourCode ()
{
HiddenColumns = new List<string> {"uiDataCountOfItems"};
}
To unhide, you only need to remove the corresponding name from the list or recreate it without the unhidden name.
I have another solution to this problem that uses an approach similar to the "Binding" property that you find on DataGridTextColumn. Since the column classes are DependencyObjects, you can't directly databind to them, BUT if you add a reference to a FrameworkElement that implements INotifyPropertyChanged you can pass a databinding through to the element, and then use a dependency property to notify the Column that the databinding has changed.
One thing to note is that having the binding on the Column itself instead of the Grid will probably mean that you will want to use a DataContextProxy to get access to the field that you want to bind the Visibility to (the column binding will default to the scope of the ItemSource).
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace XYZ.Controls
{
public class ExtendedDataGridTextColumn : DataGridTextColumn
{
private readonly Notifier _e;
private Binding _visibilityBinding;
public Binding VisibilityBinding
{
get { return _visibilityBinding; }
set
{
_visibilityBinding = value;
_e.SetBinding(Notifier.MyVisibilityProperty, _visibilityBinding);
}
}
public ExtendedDataGridTextColumn()
{
_e = new Notifier();
_e.PropertyChanged += ToggleVisibility;
}
private void ToggleVisibility(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Visibility")
this.Visibility = _e.MyVisibility;
}
//Notifier class is just used to pass the property changed event back to the column container Dependency Object, leaving it as a private inner class for now
private class Notifier : FrameworkElement, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Visibility MyVisibility
{
get { return (Visibility)GetValue(MyVisibilityProperty); }
private set { SetValue(MyVisibilityProperty, value); }
}
public static readonly DependencyProperty MyVisibilityProperty = DependencyProperty.Register("MyVisibility", typeof(Visibility), typeof(Notifier), new PropertyMetadata(MyVisibilityChanged));
private static void MyVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var n = d as Notifier;
if (n != null)
{
n.MyVisibility = (Visibility) e.NewValue;
n.PropertyChanged(n, new PropertyChangedEventArgs("Visibility"));
}
}
}
}
}
The datagrid column inherits from DependencyObject instead of FrameworkElement. In WPF this would be no big deal... but in silverlight you can only bind to FrameworkElement objects. So you get the descriptive error message of AG_E_PARSER_BAD_PROPERTY_VALUE when you try.
I don't know how much this will help, but I've run into the lack of dependency property problem with data grid columns myself in my latest project. What I did to get around it, was to create an event in the grid column view model, then when the grid is being assembled in the client, use a closure to subscribe the grid column to the column view model. My particular problem was around width. It starts with the view model class for the grid column, which looks something like this pseudo-code:
public delegate void ColumnResizedEvent(double width);
public class GridColumnViewModel : ViewModelBase
{
public event ColumnResizedEvent ColumnResized;
public void Resize(double newContainerWidth)
{
// some crazy custom sizing calculations -- don't ask...
ResizeColumn(newWidth);
}
public void ResizeColumn(double width)
{
var handler = ColumnResized;
if (handler != null)
handler(width);
}
}
Then there's the code that assembles the grid:
public class CustomGrid
{
public CustomGrid(GridViewModel viewModel)
{
// some stuff that parses control metadata out of the view model.
// viewModel.Columns is a collection of GridColumnViewModels from above.
foreach(var column in viewModel.Columns)
{
var gridCol = new DataGridTextColumn( ... );
column.ColumnResized += delegate(double width) { gridCol.Width = new DataGridLength(width); };
}
}
}
When the datagrid is resized in the application, the resize event is picked up and calls the resize method on the viewmodel the grid is bound to. This in turn calls the resize method of each grid column view model. The grid column view model then raises the ColumnResized event, which the data grid text column is subscribed to, and it's width is updated.
I realise this isn't directly solving your problem, but it was a way I could "bind" a view model to a data grid column when there are no dependency properties on it. The closure is a simple construct that nicely encapsulates the behaviour I wanted, and is quite understandable to someone coming along behind me. I think it's not too hard to imagine how it could be modified to cope with visibility changing. You could even wire the event handler up in the load event of the page/user control.
Chris Mancini,
you do not create binding to "Binding" property of data grid column. Well, you write "{Binding User.UserName}", but it doesn't create binding, because (as zachary said) datagrid column doesn't inherit from FrameworkElement and hasn't SetBinding method.
So expression "{Binding User.UserName}" simply creates Binding object and assign it to Binding property of column (this property is type of Binding).
Then datagrid column while generates cells content (GenerateElement - protected method) uses this Binding object to set binding on generated elements (e.g. on Text property of generated TextBlock) which are FrameworkElements
GreatTall1's solution is great, but it need to bit change to make it work.
var n = d as Notifier;
if (n != null)
{
//Assign value in the callback will break the binding.
//n.MyVisibility = (Visibility)e.NewValue;
n.PropertyChanged(n, new PropertyChangedEventArgs("Visibility"));
}
Note that the problem isn't just as simple as 'Visibility' not being a dependency property. In a DataGrid the columns aren't part of the visual 'tree' so you can't use AncestorType even in WPF (or Silverlight 5).
Here's a couple WPF related links (please comment if any of these work for Silverlight - sorry I don't have time to test now)
Has a really nice explanation of the problem and failures of certain solutions (and a clever solution):
http://tomlev2.wordpress.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
And a couple StackOverflow questions:
WPF Hide DataGridColumn via a binding
Binding Visible property of a DataGridColumn in WPF DataGrid
This works on a data grid template column:
public class ExtendedDataGridColumn : DataGridTemplateColumn
{
public static readonly DependencyProperty VisibilityProperty = DependencyProperty.Register("Visibility", typeof(Visibility), typeof(DataGridTemplateColumn), new PropertyMetadata(Visibility.Visible, VisibilityChanged));
public new Visibility Visibility
{
get { return (Visibility)GetValue(VisibilityProperty); }
set { SetValue(VisibilityProperty, value); }
}
private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((DataGridTemplateColumn)d != null)
{
((DataGridTemplateColumn)d).Visibility = (Visibility)e.NewValue;
}
}
}
From your MyDataGridTextColumn class, you could get the surrounding DataGrid.
Then you get your ViewModel out of the DataContext of the DataGrid and add a handler to the PropertyChanged event of your ViewModel. In the handler you just check for the property name and its value and change the Visibility of the Column accordingly.
Its not quite the best solution, but it should work ;)

Resources