I use DevExpress. I have TreeListControl. One column consists Comboboxes (implemented by ComboBoxEditSettings) with several values. When I set a value in Combobox for parent its children will be updated just when I Change Focus or press Enter, but not immidiately.
Here are some Points I have already tried:
Set an Event in ComboBoxEditSettings for an interaction, eg MouseUp;
Set an EventToCommand for eventName EditValueChanged, for example.
Binding is not working;
Set an Event on OkButton_Click, but I have
not found how to get an OkButton_Click Event in Combobox.
Create CustomButton in ComboboxEdit and set a PopUpClose Event. In this
case binding works, but I have not found how to set MyCustomButton
the OkButton Event.
Here is part of my Code. Maybe it helps to understand my case.
<dxg:TreeListControl x:Name="Tree" ItemsSource="{Binding TreeItems, Mode=TwoWay}">
<dxg:TreeListControl.Columns>
<dxg:TreeListColumn FieldName="Node"/>
<dxg:TreeListColumn FieldName="Frequency"/>
<dxg:TreeListColumn.EditSettings>
<dxe:ComboBoxEditSettings ItemsSource="{Binding Samplers}" IsTextEditable="False" DisplayMember="SamplerLongText" ValueMember="SamplerId">
<dxe:ComboBoxEditSettings.StyleSettings>
<dxe:CheckedComboBoxStyleSettings/>
</dxe:ComboBoxEditSettings.StyleSettings>
</dxe:ComboBoxEditSettings>
</dxg:TreeListColumn.EditSettings>
</dxg:TreeListColumn>
</dxg:TreeListControl.Columns>
<dxg:TreeListControl.View>
<dxg:TreeListView
x:Name="TreeListView"
AllowPerPixelScrolling="True"
ShowTotalSummary="True"
KeyFieldName="TreeItemId"
ParentFieldName="ParentId"
ShowCheckboxes="true"
CheckBoxFieldName="IsChecked"
IsCheckBoxEnabledFieldName="IsEnable"
AllowRecursiveNodeChecking="True"
ShowNodeImages="True"
AutoWidth="True"
ImageFieldName="NodeImage"/>
</dxg:TreeListControl.View>
</dxg:TreeListControl>
Thank you in advance!
We have this little helper class for the exact same functionality when using CheckEdits but it should also work for your ComboBoxEdits.
Mind that it utilizes the EditValueChanging-event under the hood which might already answer your question.
public partial class EditorCommitHelper
{
public static readonly DependencyProperty CommitOnValueChangedProperty = DependencyProperty.RegisterAttached("CommitOnValueChanged", typeof(bool), typeof(EditorCommitHelper), new PropertyMetadata(CommitOnValueChangedPropertyChanged));
public static void SetCommitOnValueChanged(GridColumn element, bool value)
{
element.SetValue(CommitOnValueChangedProperty, value);
}
public static bool GetCommitOnValueChanged(GridColumn element)
{
return (bool)element.GetValue(CommitOnValueChangedProperty);
}
private static void CommitOnValueChangedPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
GridColumn col = source as GridColumn;
if (col.View == null)
Dispatcher.CurrentDispatcher.BeginInvoke(new Action<GridColumn, bool>((column, subscribe) => {
ToggleCellValueChanging(column, subscribe);
}), col, (bool)e.NewValue);
else
ToggleCellValueChanging(col, (bool)e.NewValue);
}
private static void ToggleCellValueChanging(GridColumn col, bool subscribe)
{
TreeListView view = col.View as TreeListView;
if (view == null)
return;
if (subscribe)
view.CellValueChanging += new CellValueChangedEventHandler(view_CellValueChanging);
else
view.CellValueChanging -= view_CellValueChanging;
}
static void view_CellValueChanging(object sender, CellValueChangedEventArgs e)
{
TreeListView view = sender as TreeListView;
if ((bool)e.Column.GetValue(CommitOnValueChangedProperty))
view.PostEditor();
}
}
usage:
<dxg:GridColumn Header="your header"
FieldName="your_field"
your_xmlns:EditorCommitHelper.CommitOnValueChanged="True">
</dxg:GridColumn>
Related
I have a dropdown (ComboBox) that displays all the com ports available on a machine. Now, ports come and go when you connect and disconnect devices.
For performance reasons I don't want to keep calling System.IO.Ports.SerialPort.GetPortNames(), but rather just call that when the user clicks on the Combobox? Is this possible? Is there an MVVM approach to this problem?
Use InvokeCommandAction.
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
DropDownOpenedCommand is an ICommand property on your ViewModel.
<ComboBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="DropDownOpened">
<i:InvokeCommandAction Command="{Binding DropDownOpenedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
Edit: obviously DropDownOpened not SelectionChanged, as Patrice commented.
You can use something like MVVMLight's EventToCommand to accomplish this. Basically, the event of clicking the combo would be hooked to your MVVM command binding, which would then fire the method that calls GetPortNames().
Here are some alternatives:
MVVM Light: Adding EventToCommand in XAML without Blend, easier way or snippet? (check the accepted answer)
http://www.danharman.net/2011/08/05/binding-wpf-events-to-mvvm-viewmodel-commands/ (Prism)
What I would recommend is scrapping the 'only update on clicks' idea, and just use binding and notifications for this (unless for some reason you think there will be so many Connect/Disconnect events it will slow your system). The simplest version of that would be a dependency property.
Provide an IObservableList<Port> property as a dependency property on your ViewModel like this:
/// <summary>
/// Gets or sets...
/// </summary>
public IObservableList<Port> Ports
{
get { return (IObservableList<Port>)GetValue(PortsProperty); }
set { SetValue(PortsProperty, value); }
}
public static readonly DependencyProperty PortsProperty = DependencyProperty.Register("Ports", typeof(IObservableList<Port>), typeof(MyViewModelClass), new PropertyMetadata(new ObservableList<Port>));
Now you may add/remove items to/from that list whenever you connect or disconnect devices, just do not replace the list. This will force the list to send off a ListChangedEvent for each action on the list, and the ComboBox (or any other bound UI) will react to those events.
This should be performant enough for you, as this will only cause the UI ComboBox to update whenever an event goes through.
I took a stab at routing events to a command:
XAML:
<ComboBox
ItemsSource="{Binding Items}"
local:ControlBehavior.Event="SelectionChanged"
local:ControlBehavior.Command="{Binding Update}" />
Code:
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
namespace StackOverflow
{
public class ControlBehavior
{
public static DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(ControlBehavior));
public static DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(ControlBehavior));
public static DependencyProperty EventProperty = DependencyProperty.RegisterAttached("Event", typeof(string), typeof(ControlBehavior), new PropertyMetadata(PropertyChangedCallback));
public static void EventHandler(object sender, EventArgs e)
{
var s = (sender as DependencyObject);
if (s != null)
{
var c = (ICommand)s.GetValue(CommandProperty);
var p = s.GetValue(CommandParameterProperty);
if (c != null && c.CanExecute(s))
c.Execute(s);
}
}
public static void PropertyChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs a)
{
if (a.Property == EventProperty)
{
EventInfo ev = o.GetType().GetEvent((string)a.NewValue);
if (ev != null)
{
var del = Delegate.CreateDelegate(ev.EventHandlerType, typeof(ControlBehavior).GetMethod("EventHandler"));
ev.AddEventHandler(o, del);
}
}
}
public string GetEvent(UIElement element)
{
return (string)element.GetValue(EventProperty);
}
public static void SetEvent(UIElement element, string value)
{
element.SetValue(EventProperty, value);
}
public ICommand GetCommand(UIElement element)
{
return (ICommand)element.GetValue(CommandProperty);
}
public static void SetCommand(UIElement element, ICommand value)
{
element.SetValue(CommandProperty, value);
}
public object GetCommandParameter(UIElement element)
{
return element.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(UIElement element, object value)
{
element.SetValue(CommandParameterProperty, value);
}
}
}
I can't set keyboard focus on textbox control when my UI form loads. I am using MVVM pattern and when I tried solution on the following link but this didn't help. When the form loads there is a caret in my textbox but it's not flashing, and I can't write text in it. The same thing happens when I use FocusManager.focused element in XAML. I also tried with Keyboard.Focus(MytextBox) but the same thing happened... Please help anybody, I am stuck 2 days with this...
This is class where i made dependency property IsFocused and used it for binding with isFocused property in my viewmodel:
public static class Attached
{
public static DependencyProperty IsFocusedProperty = DependencyProperty.RegisterAttached("IsFocused", typeof(bool), typeof(Attached), new UIPropertyMetadata(false, OnIsFocusedChanged));
public static bool GetIsFocused(DependencyObject dependencyObject)
{
return (bool)dependencyObject.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject dependencyObject, bool value)
{
dependencyObject.SetValue(IsFocusedProperty, value);
}
public static void OnIsFocusedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
TextBox textBox = dependencyObject as TextBox;
bool newValue = (bool)dependencyPropertyChangedEventArgs.NewValue;
bool oldValue = (bool)dependencyPropertyChangedEventArgs.OldValue;
Keyboard.Focus(textBox);
if (newValue && !oldValue && !textBox.IsFocused) textBox.Focus();
}
}
This is my XAML:
<TextBox a:Attached.IsFocused="{Binding IsFocused, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}.../>"
For this u can use your code behind file. In the page loaded event you should do
MyTextBox.Focus();
In the Window-Element you can write
FocusManager.FocusedElement="{Binding ElementName=textBox}"
You maybe get a problem with the loaded-timing, meaning you set focus but it gets stolen afterwards. You can try to defer that by using a dispatcher:
myView.Dispatcher.BeginInvoke(new Action(() =>
{
textBox.Focus();
}), DispatcherPriority.ContextIdle);
The case is: I have a control's event that I want my ViewModel to react on. Currently I'm doing this by executing a command of invisible button like in the example below.
In View.xaml:
<Control x:Name="SearchResultGrid" ... DataRefreshed="SearchResultRefreshed" />
<Button x:Name="SearchResultRefreshedButton" Visibility="Collapsed" Command="{Binding SearchResultRefreshedCommand}" />
In View.xaml.cs:
private void SearchResultRefreshed(object sender, EventArgs e)
{
if (SearchResultRefreshedButton.Command != null)
{
SearchResultRefreshedButton.Command.Execute(SearchResultGrid.ResultRowCount);
}
}
This works good, but it looks like a hack to me. I'm wondering if there is better (standard) way of doing this? I could not find any examples and this is what I "invented" myself.
Using MVVM, the general way to handle events is to simply wrap them in Attached Properties, or use Attached Events. Here is an example using the PreviewKeyDown event in an Attached Property:
public static DependencyProperty PreviewKeyDownProperty = DependencyProperty.RegisterAttached("PreviewKeyDown", typeof(KeyEventHandler), typeof(TextBoxProperties), new UIPropertyMetadata(null, OnPreviewKeyDownChanged));
public static KeyEventHandler GetPreviewKeyDown(DependencyObject dependencyObject)
{
return (KeyEventHandler)dependencyObject.GetValue(PreviewKeyDownProperty);
}
public static void SetPreviewKeyDown(DependencyObject dependencyObject, KeyEventHandler value)
{
dependencyObject.SetValue(PreviewKeyDownProperty, value);
}
public static void OnPreviewKeyDownChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
TextBox textBox = dependencyObject as TextBox;
if (e.OldValue == null && e.NewValue != null) textBox.PreviewKeyDown += TextBox_PreviewKeyDown;
else if (e.OldValue != null && e.NewValue == null) textBox.PreviewKeyDown -= TextBox_PreviewKeyDown;
}
private static void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
TextBox textBox = sender as TextBox;
KeyEventHandler eventHandler = GetPreviewKeyDown(textBox);
if (eventHandler != null) eventHandler(sender, e);
}
Note that it is just as easy (and better too) to use an ICommand instead of the actual KeyEventArgs object which shouldn't really be in the view model. Just create an Attached Property of type ICommand and call that from this TextBox_PreviewKeyDown handler instead:
private static void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
TextBox textBox = sender as TextBox;
ICommand command = PreviewKeyDownCommand(textBox);
if (command != null && command.CanExecute(textBox)) command.Execute(textBox);
}
Either way, it would be used something like this:
<TextBox TextBoxProperties.PreviewKeyDown="SomeKeyEventHandler" />
Or if you used the preferred ICommand method:
<TextBox TextBoxProperties.PreviewKeyDownCommand="{Binding SomeCommand}" />
Personally I've never had a need to use an attached property to deal with a control's event. In your example, of a control wanting to know when the 'SearchResultRefreshed' and then informing the ViewModel through the hidden control ... why doesn't the ViewModel already know that the results have been refreshed?
If the results are coming from the ViewModel in the first place, and binding is used to display them within your control, then the knowledge that the search results have been refreshed should be driven by your ViewModel - not your view.
In only a few cases have I found a need to break away from ICommands and data-binding.
You should add a dependency property DataRefreshed to your control in order to bind on it
here an example how you can do it
public static readonly DependencyProperty DataRefreshedProperty = DependencyProperty.Register(
"DataRefreshed",
typeof(bool),
typeof("typeof yourcontrol here "),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnDataRefreshedChanged)
)
);
public bool DataRefreshed
{
get { return (bool)GetValue(DataRefreshedProperty); }
set { SetValue(DataRefreshedProperty, value); }
}
Then you can manipulate your property like any other WPF property for example SearchResultRefreshed which is defined in your ViewModel
<Control x:Name="SearchResultGrid" ... DataRefreshed="{Binding SearchResultRefreshed}" />
<Button x:Name="SearchResultRefreshedButton" Visibility="Collapsed" Command="{Binding SearchResultRefreshedCommand}" />
take a look at the following tutorial to understand more dependecyproperty and attachedproperty
I can't find an event that can be called after a window was loaded, and where i can get access to the ItemsSource of a ListView. The only thing i can think is the Loaded event in the ListView but when this event is fired the ItemsSource remains null.
I probably need another event so i can know what is in the ItemsSource.
So with code i will probably expose better what i am trying to do:
In a custom class:
public class GridViewSomething
{
public static readonly DependencyProperty TestProperty =
DependencyProperty.RegisterAttached("Test",
typeof(bool),
typeof(GridViewSomething),
new UIPropertyMetadata(OnTestChanged));
public static bool GetTest(DependencyObject obj)
{
return (bool)obj.GetValue(TestProperty);
}
public static void SetTest(DependencyObject obj, bool value)
{
obj.SetValue(TestProperty, value);
}
static void OnTestChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ListView listView = sender as ListView;
if (!(bool)e.OldValue && (bool)e.NewValue)
listView.AddHandler(ListView.LoadedEvent, new RoutedEventHandler(ListView_Loaded));
else if (!(bool)e.NewValue && (bool)e.OldValue)
listView.RemoveHandler(ListView.LoadedEvent, new RoutedEventHandler(ListView_Loaded);
}
static void ListView_Loaded(object sender, RoutedEventArgs e)
{
ListView listView = sender as ListView;
if (listView.ItemsSource != null)
{
//Do some work
}
}
}
And the ListView:
(...)
<ListView ItemsSource="{Binding Students}"
test:GridViewSomething.Test="True">
(...)
I am binding the ListView to a Collection in the ViewModel of this Window. I need to know precisely what is in the ItemsSource in that custom class.
So how i can achieve this?
Thanks in advance!
You could subsribe to changes on the ItemsSource property by using a descriptor as shown here. If check the value in that handler it should not be null (unless the bound property was in fact set to null).
I'm trying to pass the item on XamDataGrid on which I do a mouse right click to open a ContextMenu, which raises a Command in my ViewModel. Somehow the method that the Command calls is not reachable in debug mode.
This is the snipped from the view
<ig:XamDataGrid DataSource="{Binding DrdResults}" Height="700" Width="600">
<ig:XamDataGrid.ContextMenu>
<ContextMenu DataContext="{Binding RelativeSource={RelativeSource Mode=Self},
Path=PlacementTarget.DataContext}"
AllowDrop="True" Name="cmAudit">
<MenuItem Header="View History"
Command="{Binding ViewTradeHistory}"
CommandParameter="{Binding Path=SelectedItems}">
</MenuItem>
</ContextMenu>
</ig:XamDataGrid.ContextMenu>
<ig:XamDataGrid.FieldSettings>
<ig:FieldSettings AllowFixing="NearOrFar"
AllowEdit="False"
Width="auto" Height="auto" />
</ig:XamDataGrid.FieldSettings>
</ig:XamDataGrid>
My code in the corresponding ViewModel for this View is as follows.
public WPF.ICommand ViewTradeHistory
{
get
{
if (_viewTradeHistory == null)
{
_viewTradeHistory = new DelegateCommand(
(object SelectedItems) =>
{
this.OpenTradeHistory(SelectedItems);
});
}
return _viewTradeHistory;
}
}
And lastly the actual method that gets called by the Command is as below
private void OpenTradeHistory(object records)
{
DataPresenterBase.SelectedItemHolder auditRecords
= (DataPresenterBase.SelectedItemHolder)records;
// Do something with the auditRecords now.
}
I'm not sure what am I doing incorrectly here. Any help will be very much appreciated.
Thanks,
Shravan
I had that working by improving Damian answer (which was not quite working).
Here's my solution:
First the Behaviour:
public class DataGridExtender : Behavior<XamDataGrid>
{
public readonly static DependencyProperty SelectedDataItemsProperty
= DependencyProperty.Register(
"SelectedDataItems",
typeof(ICollection<object>),
typeof(DataGridExtender),
new PropertyMetadata());
public ICollection<object> SelectedDataItems
{
get { return (ICollection<object>)GetValue(SelectedDataItemsProperty); }
set { SetValue(SelectedDataItemsProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItemsChanged += AssociatedObjectOnSelectedItemsChanged;
AssociatedObjectOnSelectedItemsChanged(AssociatedObject, null);
}
protected override void OnDetaching()
{
AssociatedObject.SelectedItemsChanged -= AssociatedObjectOnSelectedItemsChanged;
base.OnDetaching();
}
private void AssociatedObjectOnSelectedItemsChanged(object sender, Infragistics.Windows.DataPresenter.Events.SelectedItemsChangedEventArgs e)
{
if (SelectedDataItems != null)
{
SelectedDataItems.Clear();
foreach (var selectedDataItem in GetSelectedDataItems())
{
SelectedDataItems.Add(selectedDataItem);
}
}
}
private IEnumerable<object> GetSelectedDataItems()
{
var selectedItems = from rec in AssociatedObject.SelectedItems.Records.OfType<DataRecord>() select rec.DataItem;
return selectedItems.ToList().AsReadOnly();
}
}
And then its usage:
<igDP:XamDataGrid>
[...]
<i:Interaction.Behaviors>
<Behaviours:DataGridExtender SelectedDataItems="{Binding SelectedDataItems, Mode=TwoWay}"></Behaviours:DataGridExtender>
</i:Interaction.Behaviors>
[...]
<igDP:XamDataGrid.FieldLayoutSettings>
[...]
</igDP:XamDataGrid.FieldLayoutSettings>
<igDP:XamDataGrid.FieldLayouts>
<igDP:FieldLayout>
[...]
</igDP:FieldLayout>
</igDP:XamDataGrid.FieldLayouts>
Of course you'll need to have a "SelectedDataItems" in your view model.
Edit: The SelectedDataItems property in the view model has to be instantited first as an empty collection, otherwise it won't work.
For a single item, infragistics was kind enough to add a bindable DependencyProperty called 'ActiveDataItem', which is "the" selected item, if any.
It even works two-way, i.e. you can reset the selection from within your ViewModel.
Unfortunately, AFAIK there is no similar thing for multi-selection.
You will have to implement this on your own, iterating over the selected records, check if they are datarecords, get the record and dataitem etc...
Try binding your DataGrid's SelectedItem to a property in your viewmodel.
You can then access this property in your OpenTradeHistory() method.
For binding to the selected items I chose to create a behavior using System.Interactivity:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Interactivity;
using Infragistics.Windows.DataPresenter;
namespace Sample {
public class DataGridExtender : Behavior<XamDataGrid> {
public readonly static DependencyProperty SelectedDataItemsProperty
= DependencyProperty.Register(
"SelectedDataItems"
, typeof(ICollection<object>)
, typeof(OzDataGridExtender)
, new PropertyMetadata(null));
public ICollection<object> SelectedDataItems {
get { return (ICollection<object>)GetValue(SelectedDataItemsProperty); }
set { SetValue(SelectedDataItemsProperty, value); }
}
protected override void OnAttached() {
base.OnAttached();
AssociatedObject.SelectedItemsChanged += AssociatedObjectOnSelectedItemsChanged;
AssociatedObjectOnSelectedItemsChanged(AssociatedObject, null);
}
protected override void OnDetaching() {
AssociatedObject.SelectedItemsChanged -= AssociatedObjectOnSelectedItemsChanged;
base.OnDetaching();
}
private void AssociatedObjectOnSelectedItemsChanged(object sender, Infragistics.Windows.DataPresenter.Events.SelectedItemsChangedEventArgs e) {
SelectedDataItems = GetSelectedDataItems();
//AssociatedObject.SetValue(SelectedDataItemsPropertyKey, SelectedDataItems);
}
private ICollection<object> GetSelectedDataItems() {
var selectedItems = from rec in AssociatedObject.SelectedItems.Records.OfType<DataRecord>()
select rec.DataItem;
return selectedItems.ToList().AsReadOnly();
}
}
}
Some where in your view would have something like the following (I've ommitted the namespace mappings for brevity):
Now your problem with the command binding on a context menu thats something else... I'll revisit this