How to catch events from WindowsApiCodePack ExplorerBrowser control in viemodel? - c#

I'm using CaliburnMicro in my WPF application, which uses an ExplorerBrowser control from WindowsAPICodePack (Microsoft-WindowsAPICodePack-Shell and Microsoft-WindowsAPICodePack-Core).
The events like SelectionChanged attached to this control are not firing in the viewmodel.
I've tried it multiple ways with Caliburn's [Event] = [Action()], or making a simpler "WinForms" style event to the backing class of the view - none of these worked.
Caliburn events are working fine for any other controls in the view. So if I place an event on the parent Grid - it works.
The only way I found it does work, is accessing the control itself by name from code-behind, which isn't how I want do things.
I'm also not entirely clear on the syntax for the Caliburn event in this case, because it's accessible through Selector - [Event Selector.SelectionChanged].
I also tried catching it with different arg types and other events with same result.
Here's the View:
<UserControl x:Class="App.WinExplorer.WinExplorerView"
xmlns:WindowsAPICodePackPresentation="clr-namespace:Microsoft.WindowsAPICodePack.Controls.WindowsPresentationFoundation;assembly=Microsoft.WindowsAPICodePack.Shell"
xmlns:cal="http://www.caliburnproject.org"
...
<Grid>
<WindowsAPICodePackPresentation:ExplorerBrowser
x:Name="ExplorerBrowser"
NavigationTarget="{Binding NavigationTarget}"
cal:Message.Attach="[Event Selector.SelectionChanged] = [Action SelectionChanged($this, $eventArgs)]"
/>
</Grid>
</UserControl>
Here's the handler in the viewmodel:
public void SelectionChanged(object s, SelectionChangedEventArgs e)
{
// never hits this method
}
Tried declaring the event the reular WPF way too:
Selector.SelectionChanged="ExplorerBrowser_SelectionChanged"
What actually works but is a backwards way of doing it - inside the code-behind:
public partial class WinExplorerView : UserControl
{
public WinExplorerView()
{
InitializeComponent();
ExplorerBrowser.SelectedItems.CollectionChanged += SelectedItems_CollectionChanged;
}
private void SelectedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
throw new System.NotImplementedException();
}
}
Any insight would be appreciated.

Related

KeyBinding works as UserControl but not when pusing property element syntax in XAML

As title states, I can't get KeyBinding to work when using property element syntax. By work I mean using the key combo of Ctrl+Del to change the background color of the list box. The key combo can be used or the button can be clicked, both of which invoke the command, yet the command is never invoked. When a breakpoint is set while in debug mode it will never be encountered.
I've followed the InputBinding Class example from the documentation and can only get KeyBinding to work when using a UserControl and would like to understand why that is, and what I'm doing wrong.
Below is an MVCE of when the code, declared with property element syntax, that does not work. Commented out is a line for a UserControl which encapsulates the StackPanel and allows the KeyBinding to work. Contingent on the commenting out each PropertyElementSyntax region and uncommenting each UserControlSyntax region in the code behind for MainWindow.xaml.cs.
MainWindow.xaml:
<Window x:Class="LearningKeyBindingWPFApp.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:LearningKeyBindingWPFApp"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">
<!--<local:UserControl1 x:Name="CustomColorPicker" />-->
<StackPanel Margin="0,40,0,0">
<StackPanel.InputBindings>
<KeyBinding Command="{Binding ChangeColorCommand}"
CommandParameter="{Binding ElementName=ColorPicker, Path=SelectedItem}"
Key="{Binding ChangeColorCommand.Key}"
Modifiers="{Binding ChangeColorCommand.ModifierKeys}" />
<MouseBinding Command="{Binding ChangeColorCommand}"
CommandParameter="{Binding ElementName=ColorPicker, Path=SelectedItem}"
MouseAction="{Binding ChangeColorCommand.MouseAction}" />
</StackPanel.InputBindings>
<Button Content="Change Color"
Command="{Binding ChangeColorCommand}"
CommandParameter="{Binding ElementName=ColorPicker, Path=SelectedItem}" />
<ListBox Name="ColorPicker"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
SelectedIndex="0">
<sys:String>Red</sys:String>
<sys:String>Green</sys:String>
<sys:String>Blue</sys:String>
<sys:String>Yellow</sys:String>
<sys:String>Orange</sys:String>
<sys:String>Purple</sys:String>
</ListBox>
</StackPanel>
</Window>
Code-behind for MainWindow.xaml.cs:
public MainWindow()
{
DataContext = this;
InitializeComponent();
InitializeCommand();
#region UserControlSyntax
//CustomColorPicker.ColorPicker.Focus();
#endregion
#region PropertyElementSyntax
ColorPicker.Focus();
#endregion
}
public SimpleDelegateCommand ChangeColorCommand { get; private set; }
private SolidColorBrush _originalColor;
private void InitializeCommand()
{
#region UserControlSyntax
//_originalColor = (SolidColorBrush)CustomColorPicker.ColorPicker.Background;
#endregion
#region PropertyElementSyntax
_originalColor = (SolidColorBrush)ColorPicker.Background;
#endregion
ChangeColorCommand = new SimpleDelegateCommand(ChangeColor)
{
Key = Key.Delete,
ModifierKeys = ModifierKeys.Control
};
}
private void ChangeColor(object colorString)
{
if (colorString == null)
{
return;
}
var selectedColor = SelectedColor((string)colorString);
#region UserControlSyntax
//if (CustomColorPicker.ColorPicker.Background == null)
//{
// CustomColorPicker.ColorPicker.Background = selectedColor;
// return;
//}
//CustomColorPicker.ColorPicker.Background = ((SolidColorBrush)CustomColorPicker.ColorPicker.Background).Color == selectedColor.Color
// ? _originalColor
// : selectedColor;
#endregion
#region PropertyElementSyntax
if (ColorPicker.Background == null)
{
ColorPicker.Background = selectedColor;
return;
}
var isColorIdentical = ((SolidColorBrush)ColorPicker.Background).Color == selectedColor.Color;
ColorPicker.Background = isColorIdentical
? _originalColor
: selectedColor;
#endregion
}
private SolidColorBrush SelectedColor(string value)
{
#region UserControlSyntax
//var selectedColor = (Color)ColorConverter.ConvertFromString(value);
#endregion
#region PropertyElementSyntax
var selectedColor = (Color)ColorConverter.ConvertFromString((string)ColorPicker.SelectedItem);
#endregion
return new SolidColorBrush(selectedColor);
}
The problem is that in the no-UserControl scenario, the DataContext is set before the command object has been initialized.
WPF has a robust binding system, but it normally relies on property-change notifications, via INotifyPropertyChanged. Some scenarios will work without that, as long as you get the order of operations correct. But, without property-change notifications, if you miss your window of opportunity to present some property value to WPF, it's not going to try again later.
When you use the UserControl, the initialization of the bindings for the UserControl occurs after you set up the ChangeColorCommand property. This is just an artifact of how WPF initializes the various objects in the UI tree. But it means that by the time the UserControl's bindings look at the ChangeColorCommand property, it has the value you want.
On the other hand, when you put the StackPanel explicitly into the window's XAML, it's too late by the time you set the property for WPF to see it. It already resolved those bindings during the InitializeComponent() call. Setting the property later has no effect.
There are a couple of ways you could address that given the code you have now:
The simplest is to just move the assignment of DataContext = this; to after the call to InitializeCommand(). Updating the DataContext requires WPF to update all of the dependent bindings too, so doing that after the InitializeCommand() call ensures the property has the value you want.
Implement INotifyPropertyChanged in the MainWindow class, and raise the PropertyChanged event for the ChangeColorCommand property when you set it. This will let WPF know that the value changed and that it should re-evaluate any bindings that depended on it.
All that said, I'd go one further:
Implement a proper view model object, with INotifyPropertyChanged and a ChangeColorCommand, and use that as the data context. Making your UI objects do double-duty as both UI and property binding source (i.e. the view model's job) doesn't fit with the normal WPF model, sacrifices the benefits that MVVM would normally provide, and of course introduces this kind of weird timing thing where it's not obvious why a property binding isn't working as expected.
Okay, technically there's a fourth approach you could take, which is to put the call to InitializeCommand() before InitializeComponent(). Main problem with that is, at the moment, it relies on retrieving directly the value of a UI object's property, and that UI object won't exist until after InitializeComponent() is called.
Which brings me back to the #3 option above. Fact is, you shouldn't be accessing UI object properties directly. That should be another property in your view model, and you should make a more direct choice about what that initial color should be, than just grabbing it from the UI on startup.
I admit, there's some wiggle room for design here, but you should be trying to keep your view model and UI code as divorced from each other as possible.

Application Navigation through a single frame

I am currently making an c# wpf application in which a task is need to do step-by-step through a single frame, like software installation. Has anyone got idea how to make it?
The way I did it on a recent application I made was to have the main window have 3 parts, a header, the main content, and a footer. Each one had a ContentControl that I bound so that in my ViewModel I could change the content being displayed dynamically. In the view model I loaded up the appropriate display based on the state of the program.
If I was at screen Welcome, I loaded up the appropriate header, footer, and content for the welcome screen. The XAML looked like this for each part:
<ContentControl x:Name="ContentHeader" Content="{Binding Path=ContentHeader}" Grid.Row="0"/>
<ContentControl x:Name="ContentMain" Content="{Binding Path=ContentMain}" Grid.Row="1"/>
<ContentControl x:Name="ContentFooter" Content="{Binding Path=ContentFooter}" Grid.Row="2"/>
In my code then I made new instances as necessary:
if (_screen == Constants.Screens.Welcome)
{
ContentHeader = new HeaderWelcome();
ContentMain = new MainWelcome();
ContentFooter = new FooterWelcome();
}
else if (_screen == Constants.Screens.Setup)
{
ContentHeader = new HeaderSetup();
ContentMain = new MainSetup();
ContentFooter = new FooterSetup();
}
Each part, MainWelcome, Main Setup, FooterWelcome, etc, are all just user controls I created.
This lets each of the parts have very little to do themselves. Buttons on those panels raised custom events to bubble up to the view model. Like so:
Define the custom event:
public static readonly RoutedEvent WelcomeEvent=
EventManager.RegisterRoutedEvent("WelcomeEvent", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(UserControl));
Call the custom event (from a button in this example):
private void ButtonWelcome_Click(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(Events.TestTintEvent));
}
Finally, tie the event to a function in your view model. This can be done in the MainWindow's constructor:
AddHandler(Events.WelcomeEvent, new RoutedEventHandler(ViewModel.GetInstance().Welcome));
Then I just have the function Welcome defined in my view model and do the work there:
public void Welcome(object sender, RoutedEventArgs e)
{
//Do Work
}
This may not be the most optimal way to do it, but it was how I did it, and it worked well in my situation.

How to bind a command to Window Loaded event using WPF and MVVM

There is a button which is binded to a command like this on my WPF window :
<Button Command="{Binding SearchCommand}">
I want that Command to be executed when that window is loaded.
I added this to xaml :
Loaded="DXWindow_Loaded">
And I added this to code-behind, but I don't know what to write to fill the method.
private void DXWindow_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
}
How can I call that SearchCommand or SearchExecute from code-behind? Thanks.
I assume that you bind your ViewModel to your View's DataContext.
var vm = this.DataContext as YourViewModel;
vm.SearchCommand.Execute(null);

Binding sometimes fails, depending on position of {Binding} in XAML file

My program contains several instances of this line:
<local:MyClass Data="{Binding}"/>
I.e. the property Data is bound to the data context of the surrounding window. When the value of the window's DataContext changes, the binding is sometimes updated, sometimes not; it depends on the position of <local:MyClass...> in the XAML file.
Here is an example (EDIT: I changed the {Binding} to {Binding Path=DataContext, ElementName=myWindow} to emphasise that the problem is not related to inheritance of DataContext):
XAML code:
<Window x:Class="BindTest.MainWindow"
x:Name="myWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindTest"
Title="Binding Test" Height="101" Width="328">
<Window.Tag>
<local:MyClass x:Name="bindfails" Data="{Binding Path=DataContext, ElementName=myWindow}"/>
</Window.Tag>
<StackPanel Orientation="Horizontal">
<Button Margin="5" Padding="5" Click="SetButtonClicked">Set DataContext</Button>
<Button Margin="5" Padding="5" Click="ReadButtonClicked">Read Bound Property</Button>
<local:MyClass x:Name="bindworks" Data="{Binding Path=DataContext, ElementName=myWindow}"/>
</StackPanel>
</Window>
C# code:
using System.Windows;
namespace BindTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void SetButtonClicked(object sender, RoutedEventArgs e)
{
DataContext = 1234;
}
private void ReadButtonClicked(object sender, RoutedEventArgs e)
{
string txtA = (bindfails.Data == null ? "null" : bindfails.Data.ToString());
string txtB = (bindworks.Data == null ? "null" : bindworks.Data.ToString());
MessageBox.Show(string.Format("bindfails.Data={0}\r\nbindworks.Data={1}", txtA, txtB));
}
}
public class MyClass : FrameworkElement
{
#region Dependency Property "Data"
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(MyClass), new UIPropertyMetadata(null));
#endregion
}
}
First press button "Set DataContext" to change the data context. Then press button "Read Bound Property", which display this message:
bindfails.Data=null
bindworks.Data=1234
Obviously, the data binding was only updated for the MyClass element that is child of the StackPanel; but the data binding was not updated for the MyClass element that is referenced by Window.Tag.
EDIT2: I also have found out that binding works when adding the binding programmatically inside MainWindow's constructor:
Binding binding = new Binding("DataContext") {Source = this};
bindfails.SetBinding(MyClass.DataProperty, binding);
The binding only fails when it is declared in XAML. Furthermore, the problem is not specific to DataContext; it also happens when I use other Window properties, such as Title.
Can anyone explain this behavior and suggest how to allow the use of {Binding} in XAML in both cases?
EDIT3: The above code is not entirely equivalent to the {Binding} markup extension. The 100% equivalent code is:
Binding binding = new Binding("DataContext") {ElementName = "myWindow"};
bindfails.SetBinding(MyClass.DataProperty, binding);
When I use that code, binding also fails (like when binding in XAML) and the following diagnostics message is written to debug output:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=myWindow'.
Obviously, the ElementName property only searches up the visual tree or logical tree, even though this is not documented in the WPF online documentation. Probably, there is no easy way to set such a binding in XAML.
The dataContext is passed only throught the Object Tree. The Property tag is not in the visual tree and will not respond to DataContext changed Event and the binding is not refreshed without this event.
See :
Dependency property identifier field: DataContextProperty from the FrameworlElement
Data context is a concept that allows objects to inherit binding-specifying information from their parents in the object tree
http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.datacontext(v=vs.95).aspx
I suppose it does not work because when you write {Binding}, the context of the data to be bound is inherited from the parent. In case of bindworks, it's a StackPanel, which inherits DataContext from Window, however bindfails.Parent property is null.
I wonder why have you put a control in Window's tag element. If you must keep it declared in Tag node for some reason, you can update its DataContext directly, so just change SetButtonClicked method to:
private void SetButtonClicked(object sender, RoutedEventArgs e)
{
DataContext = 1234;
bindfails.DataContext = DataContext;
}
Another simple method to make it work is just taking bindfails out of Window.Tag and placing it somewhere in the Window, i.e. in the StackPanel.
Next, in the Window's constructor, write this.Tag = bindfails. If you don't want the TextBox to appear in the form, you can set its Visibility to Collapsed (or put it inside a collapsed container control).

WP7 Toolkit - How can I ignore events like ListPicker's "SelectionChanged" and ToggleSwitch's "Checked" events when changed by code (not user input)?

I'm new to XAML and C#, but have been enjoying working with it in the couple of weeks I've played with it. I've started working on an App and have put together a simple "Settings" page in XAML; now I'm trying to hook up events to the controls to (a) update application state when the user interacts with them, and (b) have the current state upon visiting the page.
I've hit two (related) road-blocks though:
the toolkit:ListPicker control doesn't seem to work well when I define the "ListPickerItem"s in XAML, so in the SettingsPage constructor, I set the contents manually:
lpColour.ItemsSource = new List<string>()
{
"Red","Blue","Green","Custom…"
};
lpColour.SelectedIndex = 1; // set the currently selected item to "Blue"
However, because the control (lpColour in this example) has an Event on SelectionChanged, two events get fired (one with "Red" selected as the box is populated, then another when "Blue" is selected). I don't want to process the "SelectionChanged" at this moment; only when the user has interacted with the control themselves (eg. if they select "Custom…", I can reveal a separate text-box and give that focus; but I don't want to do that when I'm setting up the page and they had "Custom…" previously selected, as otherwise the user gets the keyboard appearing as soon as they open the Settings page...)
Similarly, I've found that ToggleSwitch controls will fire "Checked" and "Unchecked" events when the "IsChecked" Property is changed to something new. Again, is there a way to ignore or suppress this event when changed by code? (I kind-of got around this for now by just using "Clicked", but from a learning standpoint, it'd be nice to know how to deal with it).
I was thinking maybe there was some way to get the "origin" (eg. "code" or "user input") of the Event from the "SelectionChangedEventArgs" or "RoutedEventArgs"... but maybe not?
I also tried setting an "initialized" bool value ("false" by default, set to "true" after Constructor is run, and wrap the Event-handling code in something like "if (initialized) { ... }"; but the event still seemed to be fired after the Constructor was done for the "lpColour.ItemSource=..." and "lpColour.SelectedIndex = 1" code that was done while "initialized" was "false". Very strange. :P
I hope I'm explaining that clearly - I've never posted here before!
I'd appreciate any help you could offer. Thanks!
UPDATE - thanks to #MyKuLLSKI's answer, that's given me a great place to work from.
As a side note, building on the idea, I tried keeping them as 'List's initially and having the "IgnoreSelectionChanged" as an int that would 'count down' (so before setting the ListPicker's ItemSource, I'd set "IgnoreSelectionChanged+=2" (to account for the two events that would get fired); similarly I'd set "IgnoreSelectionChanged++" just before setting the SelectedIndex manually... that seemed to work too.
However, using the "ObservableCollection" bound to the ListPicker and relying on that to tell of changes seems perhaps a better way than using the ListPicker's own "SelectionChanged" event, so I'll modify my code to use that instead.
Thanks again!
I'll try to answer all your questions/problems
The reason why you are having trouble setting the ItemSource in XAML is because im almost certain you have some Binding issues. For Bindings to work you need to have a DataContext and Binding on a UIElement.
Something that is bought to a property must be a DependencyProperty or INotifyPropertyChanged
Also a List is not a good Collection type to bind a ListPicker to. Instead you would probably want to use as ObservableCollextion() instead. This if this collection is bound to the ListPicker and the items change the ListPicker will be automatically updated.
The reason why the SelectionChanged Event gets fired 2 times is because you are changed it twice. When the ListPicker is first created the Selected item is null or -1 because no items are in it. Then when you set the ItemSource it automatically changed the SelectedIndex to 0 then you change it to 1.
One way is to add a flag every time the user you know your changing the variable in code
Silverlight lacks an IsLoaded Property so you ma want to add a bool when the Page gets loaded to true.
When Binding doesn't change the property in the UIElement. Instead change the property its bound to.
Below is my solution that should solve all your issues (WP7.1):
XAML
<phone:PhoneApplicationPage
x:Class="WP7Sandbox.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True"
Loaded="PhoneApplicationPageLoaded">
<Grid>
<StackPanel>
<toolkit:ListPicker ItemsSource="{Binding ListPickerCollection, Mode=TwoWay}" SelectionChanged="ListPickerSelectionChanged" SelectedIndex="{Binding ListPickerSelectedIndex, Mode=TwoWay}"/>
<Button Click="ButtonClick" Content="Selection Change and Ignore Event"/>
<Button Click="Button2Click" Content="Selection Change and Trigger Event"/>
<toolkit:ToggleSwitch IsChecked="{Binding ToggleSwitchValue, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</phone:PhoneApplicationPage>
Code Behind
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls;
namespace WP7Sandbox
{
public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private bool IsLoaded;
private bool IgnoreSelectionChanged;
public ObservableCollection<string> ListPickerCollection { get; private set; }
private bool _ToggleSwitchValue;
public bool ToggleSwitchValue
{
get
{
return _ToggleSwitchValue;
}
set
{
_ToggleSwitchValue = value;
OnPropertyChanged("ToggleSwitchValue");
}
}
private int _ListPickerSelectedIndex;
public int ListPickerSelectedIndex
{
get
{
return _ListPickerSelectedIndex;
}
set
{
_ListPickerSelectedIndex = value;
OnPropertyChanged("ListPickerSelectedIndex");
}
}
public MainPage()
{
InitializeComponent();
ListPickerCollection = new ObservableCollection<string>()
{
"Red",
"Blue",
"Green",
"Custom…"
};
}
private void PhoneApplicationPageLoaded(object sender, RoutedEventArgs e)
{
IsLoaded = true;
}
private void ListPickerSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (IsLoaded && !IgnoreSelectionChanged)
{
}
IgnoreSelectionChanged = false;
}
private void ButtonClick(object sender, RoutedEventArgs e)
{
// I want to ignore this SelectionChanged Event
IgnoreSelectionChanged = true;
ChangeListPickerSelectedIndex();
}
private void Button2Click(object sender, RoutedEventArgs e)
{
// I want to trigger this SelectionChanged Event
IgnoreSelectionChanged = false; // Not needed just showing you
ChangeListPickerSelectedIndex();
}
private void ChangeListPickerSelectedIndex()
{
if (ListPickerSelectedIndex - 1 < 0)
ListPickerSelectedIndex = ListPickerCollection.Count - 1;
else
ListPickerSelectedIndex--;
}
}
}
A lot is there but it should help

Categories