How do I capture key down in WPF? - wpf

How do I capture a key down event in WPF even if my application is not focused?

For me, the best way is this:
public MainWindow()
{
InitializeComponent();
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
if ((Keyboard.GetKeyStates(Key.W) & KeyStates.Down) > 0)
{
player1.walk();
}
}
The rendering event runs every time.

Global keyboard hook can slow down your debugging.
I prefer to use this approach:
Create KeyboardListener class
public class KeyboardListener : IDisposable
{
private readonly Thread keyboardThread;
//Here you can put those keys that you want to capture
private readonly List<KeyState> numericKeys = new List<KeyState>
{
new KeyState(Key.D0),
new KeyState(Key.D1),
new KeyState(Key.D2),
new KeyState(Key.D3),
new KeyState(Key.D4),
new KeyState(Key.D5),
new KeyState(Key.D6),
new KeyState(Key.D7),
new KeyState(Key.D8),
new KeyState(Key.D9),
new KeyState(Key.NumPad0),
new KeyState(Key.NumPad1),
new KeyState(Key.NumPad2),
new KeyState(Key.NumPad3),
new KeyState(Key.NumPad4),
new KeyState(Key.NumPad5),
new KeyState(Key.NumPad6),
new KeyState(Key.NumPad7),
new KeyState(Key.NumPad8),
new KeyState(Key.NumPad9),
new KeyState(Key.Enter)
};
private bool isRunning = true;
public KeyboardListener()
{
keyboardThread = new Thread(StartKeyboardListener) { IsBackground = true };
keyboardThread.Start();
}
private void StartKeyboardListener()
{
while (isRunning)
{
Thread.Sleep(15);
if (Application.Current != null)
{
Application.Current.Dispatcher.Invoke(() =>
{
if (Application.Current.Windows.Count > 0)
{
foreach (var keyState in numericKeys)
{
if (Keyboard.IsKeyDown(keyState.Key) && !keyState.IsPressed) //
{
keyState.IsPressed = true;
KeyboardDownEvent?.Invoke(null, new KeyEventArgs(Keyboard.PrimaryDevice, PresentationSource.FromDependencyObject(Application.Current.Windows[0]), 0, keyState.Key));
}
if (Keyboard.IsKeyUp(keyState.Key))
{
keyState.IsPressed = false;
}
}
}
});
}
}
}
public event KeyEventHandler KeyboardDownEvent;
/// <summary>
/// Состояние клавиши
/// </summary>
private class KeyState
{
public KeyState(Key key)
{
this.Key = key;
}
public Key Key { get; }
public bool IsPressed { get; set; }
}
public void Dispose()
{
isRunning = false;
Task.Run(() =>
{
if (keyboardThread != null && !keyboardThread.Join(1000))
{
keyboardThread.Abort();
}
});
}
}
Subscribe to KeyboardDownEvent in code-behind (or where you need it).
public partial class MainWindow : Window
{
private KeyboardListener listener;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
listener = new KeyboardListener();
listener.KeyboardDownEvent += ListenerOnKeyPressed;
}
private void ListenerOnKeyPressed(object sender, KeyEventArgs e)
{
// TYPE YOUR CODE HERE
}
private void Window_OnUnloaded(object sender, RoutedEventArgs e)
{
listener.KeyboardDownEvent -= ListenerOnKeyPressed;
}
}
Done

See this questions for hooking the keyboard Using global keyboard hook (WH_KEYBOARD_LL) in WPF / C#

Related

Xamarin binding does not work in Behavior<>

I made Behavior for transfer image to the ViewModel property.
When the user сlicks on the element, the gallery will open. When the user chooses some image from gallery, my ImageBytes will have bytes of this image. But after I assign a new value to the property, it is not passed to my VM.
My view model does not respond to changes in the Behavior.
public class FolderDialogBehavior : Behavior<View>
{
public byte[] ImageBytes
{
get { return (byte[])GetValue(ImageBytesProperty); }
private set
{
SetValue(ImageBytesProperty, value);
}
}
public readonly static BindableProperty ImageBytesProperty = BindableProperty.Create(nameof(ImageBytes), typeof(byte[]),
typeof(FolderDialogBehavior), null, BindingMode.TwoWay);
private TapGestureRecognizer tapGestureRecognizer = new TapGestureRecognizer()
{
NumberOfTapsRequired = 1
};
protected override void OnAttachedTo(View view)
{
base.OnAttachedTo(view);
tapGestureRecognizer.Tapped += OnTapGestureRecognizerTapped;
view.GestureRecognizers.Add(tapGestureRecognizer);
}
protected override void OnDetachingFrom(View view)
{
base.OnDetachingFrom(view);
tapGestureRecognizer.Tapped -= OnTapGestureRecognizerTapped;
view.GestureRecognizers.Remove(tapGestureRecognizer);
}
private void OnTapGestureRecognizerTapped(object sender, EventArgs e)
{
GetPhotoAsync();
}
private async void GetPhotoAsync()
{
try
{
var photo = await MediaPicker.PickPhotoAsync();
byte[] bytes;
using (Stream sourceStream = await photo.OpenReadAsync())
{
bytes = new byte[sourceStream.Length];
await sourceStream.ReadAsync(bytes, 0, (int)sourceStream.Length);
}
ImageBytes = bytes;
}
catch (Exception ex)
{
//await DisplayAlert("Сообщение об ошибке", ex.Message, "OK");
}
}
}
<Frame>
<Frame.Behaviors>
<local:FolderDialogBehavior ImageBytes="{Binding AddEmployee.UserImage, Mode=TwoWay}"/>
</Frame.Behaviors>
</Frame>
public class EmployeeViewModel : OnPropertyChangedClass
{
private byte[] _userImage;
public byte[] UserImage
{
get => _userImage;
// *** I don't get here with debugging.***
set => SetProperty(ref _userImage, value);
}
}
public abstract class OnPropertyChangedClass : INotifyPropertyChanged
{
/// <inheritdoc cref="INotifyPropertyChanged"/>
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetProperty<T>(ref T propertyFiled, T newValue, [CallerMemberName] string propertyName = null)
{
if (!object.Equals(propertyFiled, newValue))
{
T oldValue = propertyFiled;
propertyFiled = newValue;
RaisePropertyChanged(propertyName);
OnPropertyChanged(propertyName, oldValue, newValue);
}
}
protected virtual void OnPropertyChanged(string propertyName, object oldValue, object newValue) { }
}
protected override void OnAttachedTo(View bindable)
{
BindingContext = bindable.BindingContext;
bindable.BindingContextChanged += Bindable_BindingContextChanged;
/* Some Code */
base.OnAttachedTo(bindable);
}
protected override void OnDetachingFrom(View bindable)
{
bindable.BindingContextChanged -= Bindable_BindingContextChanged;
/* Some Code */
base.OnDetachingFrom(bindable);
}
private void Bindable_BindingContextChanged(object sender, EventArgs e)
{
if (sender is View view)
{
BindingContext = (sender as View).BindingContext;
}
}

WPF: store objects inside application settings.settings file

I build a simple ClipboardManager that hold all last Copy item.
So i have this simple ClipboardItem class:
public class ClipboardItem : INotifyPropertyChanged
{
private string _text { get; set; }
private int _index { get; set; }
public string Text
{
get { return _text; }
set
{
_text = value;
NotifyPropertyChanged();
}
}
public int Index
{
get { return _index; }
set
{
_index = value;
NotifyPropertyChanged();
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
And my ViewModel class that hold ObservableCollection<ClipboardItem>:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ClipboardItem> _clipboards;
public ViewModel()
{
if (_clipboards == null)
{
_clipboards = new ObservableCollection<ClipboardItem>();
_clipboards.CollectionChanged += _clipboards_CollectionChanged;
}
}
private void _clipboards_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
for (int i = 0; i < _clipboards.Count; i++)
_clipboards[i].Index = i + 1;
}
public ObservableCollection<ClipboardItem> Clipboards
{
get { return _clipboards; }
set
{
_clipboards = value;
NotifyPropertyChanged();
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
So every Copy create new ClipboardItem object inside list but when i restart the application all the records gone so i wonder if there any way to store all my ClipboardItem object inside the application settings.settings file.
//Variable Creation
private string _dataFileName = #"ClipData.xml";
DataTable _clipDataTable = new DataTable();
Inside ViewModel constructor.
public ViewModel()
{
if (_clipboards == null)
{
_clipboards = new ObservableCollection<ClipboardItem>();
_clipboards.CollectionChanged += _clipboards_CollectionChanged;
}
InitDataTable();
ReadDataFile();
}
Create new Methods
/// <summary>
/// Initialize Data Table considering you have only 1 column data.
/// If you have more then you need to create more columns
/// </summary>
private void InitDataTable()
{
_clipDataTable = new DataTable();
_clipDataTable.Columns.Add("ClipHeader");
_clipDataTable.AcceptChanges();
}
//the clipboard Data is saved in xml file.
private void WriteDataFile()
{
DataSet ClipDataSet = new DataSet();
ClipDataSet.Tables.Add(_clipDataTable);
ClipDataSet.WriteXml(_dataFileName);
}
// if file exits then read the xml file and add it to the Collection, which will be reflected in UI.
private void ReadDataFile()
{
DataSet ClipDataSet = new DataSet();
if (File.Exists(_dataFileName))
{
ClipDataSet.ReadXml(_dataFileName);
foreach (DataRow item in ClipDataSet.Tables[0].Rows)
{
Clipboards.Add(new ClipboardItem { Text = Convert.ToString(item["ClipHeader"]) });
}
}
}
Using Command you can bind Method of ViewModel to Window Closing event. So whenever the user closes the window, the data in the collection will be written into the Xml file.
Data from the Collection is copied into DataTable.
private void WindowCloseCommadn(object o)
{
foreach (var item in Clipboards)
{
DataRow dataRow = _clipDataTable.NewRow();
dataRow["ClipHeader"] = item.Text;
_clipDataTable.Rows.Add(dataRow);
}
WriteDataFile();
}
Update:-
Same Code without ViewModel, by making the codebehind class has the DataContext for binding Collection.
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ClipboardMonitor clipboardMonitor;
private string _dataFileName = #"ClipData.xml";
DataTable _clipDataTable = new DataTable();
public ObservableCollection<ClipboardItem> Clipboards { get; set; }
public MainWindow()
{
InitializeComponent();
Clipboards = new ObservableCollection<ClipboardItem>();
Clipboards.CollectionChanged += Clipboards_CollectionChanged;
Loaded += MainWindow_Loaded;
InitiateClipboardMonitor();
this.Closing += MainWindow_Closing1;
this.DataContext = this;
}
private void MainWindow_Closing1(object sender, CancelEventArgs e)
{
foreach (var item in Clipboards)
{
DataRow dataRow = _clipDataTable.NewRow();
dataRow["ClipHeader"] = item.Text;
_clipDataTable.Rows.Add(dataRow);
}
WriteDataFile();
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
InitDataTable();
ReadDataFile();
}
/// <summary>
/// Initialize Data Table considering you have only 1 column data.
/// If you have more then you need to create more columns
/// </summary>
private void InitDataTable()
{
_clipDataTable = new DataTable();
_clipDataTable.Columns.Add("ClipHeader");
_clipDataTable.AcceptChanges();
}
private void WriteDataFile()
{
DataSet ClipDataSet = new DataSet();
ClipDataSet.Tables.Add(_clipDataTable);
ClipDataSet.WriteXml(_dataFileName);
}
private void ReadDataFile()
{
DataSet ClipDataSet = new DataSet();
if (File.Exists(_dataFileName))
{
ClipDataSet.ReadXml(_dataFileName);
foreach (DataRow item in ClipDataSet.Tables[0].Rows)
{
Clipboards.Add(new ClipboardItem { Text = Convert.ToString(item["ClipHeader"]) });
}
}
}
private void Clipboards_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
for (int i = 0; i < Clipboards.Count; i++)
{
Clipboards[i].Index = i + 1;
}
}
private void InitiateClipboardMonitor()
{
clipboardMonitor = new ClipboardMonitor();
clipboardMonitor.OnClipboardContentChanged += ClipboardMonitor_OnClipboardContentChanged; ;
}
private void ClipboardMonitor_OnClipboardContentChanged(object sender, EventArgs e)
{
string clipboardText = Clipboard.GetText(TextDataFormat.Text);
Clipboards.Add(new ClipboardItem { Text = clipboardText });
}
}
to know about Command, refer the article
https://www.codeproject.com/Articles/274982/Commands-in-MVVM

Awesomium 1.7.5.1, WinForms, WebControl obj.ExecuteJavascript

I want to insert html code into existing html code.
But I do not see the result. Here is the code C #:
1) Program.cs
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
wUI.DocumentReady += wUI_DocumentReady;
}
private void Form1_Load(object sender, EventArgs e)
{
// code here ?
}
void wUI_DocumentReady(object sender, DocumentReadyEventArgs e)
{
wUI.LoadHTML("<html><body>sadasdsad</body></html>");
HtmlManager html = HtmlManager.Instance;
string[] placeholders = { "asset://customdatastore/path/to/any", "type-button", "no-action", "Example link" };
html.Add("{3}", placeholders);
html.InnerCode(html.Code, wUI, "body");
wUI.Refresh();
}
}
2) HtmlManager.cs
public sealed class HtmlManager
{
private static readonly Lazy<HtmlManager> InstanceField = new Lazy<HtmlManager>(() => new HtmlManager());
private StringBuilder _stringBuilder = null;
public string Code { get { return _stringBuilder.ToString(); } }
private HtmlManager()
{
if (_stringBuilder != null)
_stringBuilder.Clear();
_stringBuilder = new StringBuilder();
}
public static HtmlManager Instance { get { return InstanceField.Value; } }
public void Add(string row, string[] placeholders = null)
{
if (placeholders != null)
_stringBuilder.AppendLine(string.Format(row, placeholders));
_stringBuilder.AppendLine(row);
}
public void InnerCode(string code, object sender, string afterTag = "html")
{
Awesomium.Windows.Forms.WebControl ui = (Awesomium.Windows.Forms.WebControl)sender;
ui.ExecuteJavascript(string.Format("document.getElementsByTagName({0})[0].innerHTML({1})", afterTag, code));
}
public void Clear()
{
_stringBuilder.Clear();
}
}
The event (DocumentReady) does not happen, I do not believe, maybe I'm wrong somewhere?
UP: I try do it:
private void Form1_Load(object sender, EventArgs e)
{
wUI.LoadHTML("<html><body>sadasdsad</body></html>");
}
void wUI_DocumentReady(object sender, DocumentReadyEventArgs e)
{
HtmlManager html = HtmlManager.Instance;
string[] placeholders = { "asset://customdatastore/path/to/any", "type-button", "no-action", "Example link" };
html.Add("{3}", placeholders);
wUI.ExecuteJavascript("document.getElementsByTagName('body').innerHTML(\"sometext\")");
//html.InnerCode(html.Code, wUI, "body");
//wUI.Refresh();
}
No result
UP 2:
public void Add(string row, string[] placeholders = null)
{
if (placeholders != null)
_stringBuilder.AppendLine(string.Format(row, placeholders));
if (placeholders == null)
_stringBuilder.AppendLine(row);
}
UP 3:
Work with:
wUI.Source = new Uri(#"http://google.com");
in Form1_Load
You can use LoadHtml method, but only after document is fully loaded (don't confuse with DocumentReadyState.Ready) It works for me at least:
private void WebControl_DocumentReady(object sender, DocumentReadyEventArgs e)
{
if (e.ReadyState != DocumentReadyState.Loaded) return;
}
But as an initialisation, you should use Source property, like you wrote in your third update

WPF ListBox SelectedItems determine Ctrl or Shift pressed for selection

I need to know, if the SelectedItems got filled when Ctrl or Shift was pressed or not. Is there an easy way (without creating a new controltemplate) to get this info? I prefer solutions without code behind.
Best regards
Yannik
You can wire up selection changed event, and check if Modifier Keys are pressed for Selection.
void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var isCtrlorShiftDown = (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl) || Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift));
if (isCtrlorShiftDown)
{
// Write your Logic Here;
}
}
I found a solution with minimal code behind.
The main concept is, that I attach to KeyDown and KeyUp events in the MainWindow and set a property "CurrentKeyboardKeyPressed" on the MainViewModel, which propagates the pressed key info to the child view models, which in turn fire a custom event with a special Selection class that has the currently pressed key info.
The posted source code is heavyli shortened and does not run at all. If somebody is interested in the working solution, just ask me and I will email it.
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private readonly MainWindowViewModel _mainWindowViewModel;
public MainWindow()
{
_mainWindowViewModel = new MainWindowViewModel();
InitializeComponent();
DataContext = _mainWindowViewModel;
}
private void MainWindow_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl)
{
_mainWindowViewModel.CurrentKeyboardKeyPressed = PressedKeyboardKey.Ctrl;
return;
}
if (e.Key == Key.LeftShift || e.Key == Key.RightShift)
{
_mainWindowViewModel.CurrentKeyboardKeyPressed = PressedKeyboardKey.Shift;
}
}
private void MainWindow_OnKeyUp(object sender, KeyEventArgs e)
{
_mainWindowViewModel.CurrentKeyboardKeyPressed = PressedKeyboardKey.None;
}
}
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModelBase
{
// child view models
private readonly ObservableCollection<TTSViewModel> _ttsViewModels;
private PressedKeyboardKey _currentKeyboardKeyPressed;
public EventHandler<KeyboardKeyPressedEventArgs> CurrentKeyboardKeyPressedChanged;
public MainWindowViewModel()
{
_ttsViewModels = new ObservableCollection<TTSViewModel>();
}
public PressedKeyboardKey CurrentKeyboardKeyPressed
{
get { return _currentKeyboardKeyPressed; }
set
{
if (_currentKeyboardKeyPressed != value)
{
_currentKeyboardKeyPressed = value;
OnCurrentKeyboardKeyPressedChanged(_currentKeyboardKeyPressed);
}
}
}
// create child view models
public void PopulateTTSList(int itemsToCreated)
{
for (int i = 0; i < itemsToCreated; i++)
{
var tts = new TTSViewModel("TTS " + i);
tts.FractionSelectionChanged += OnTTSFractionSelectionChanged;
CurrentKeyboardKeyPressedChanged += tts.CurrentKeyboardKeyPressedChanged;
_ttsViewModels.Add(tts);
}
}
private void OnCurrentKeyboardKeyPressedChanged(PressedKeyboardKey currentKeyboardKeyPressed)
{
var handler = CurrentKeyboardKeyPressedChanged;
if (handler != null)
{
handler(this, new KeyboardKeyPressedEventArgs(currentKeyboardKeyPressed));
}
}
// selection changed handler for each child view model
private void OnTTSFractionSelectionChanged(object sender, ItemSelectionChangedEventArgs fractionSelectionChangedEventArgs)
{
var sendingTTS = sender as TTSViewModel;
if (fractionSelectionChangedEventArgs.Selection.PressedKeyOnSelection == PressedKeyboardKey.None)
{
// single selection action goes here
// ....
}
else
{
// multi selection action goes here
// ....
}
}
}
TTSViewModel.cs (child view model)
public class TTSViewModel : ViewModelBase
{
private readonly SmartObservableCollection<FractionViewModel> _currentlySelectedfractions;
public EventHandler<ItemSelectionChangedEventArgs> FractionSelectionChanged;
private PressedKeyboardKey _currentKeyboardKeyPressed;
public TTSViewModel()
{
_currentlySelectedfractions = new SmartObservableCollection<FractionViewModel>();
_currentlySelectedfractions.CollectionChanged += CurrentlySelectedfractionsOnCollectionChanged;
}
public void CurrentKeyboardKeyPressedChanged(object sender, KeyboardKeyPressedEventArgs currentKeyboardKeyPressedEventArgs)
{
_currentKeyboardKeyPressed = currentKeyboardKeyPressedEventArgs.CurrentKeyboardKeyPressed;
}
private void CurrentlySelectedfractionsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
if (FractionSelectionChanged != null)
{
if (notifyCollectionChangedEventArgs.Action == NotifyCollectionChangedAction.Replace && notifyCollectionChangedEventArgs.NewItems != null)
{
var removed = _oldSelectedfractions.Except(_currentlySelectedfractions);
var added = _currentlySelectedfractions.Except(_oldSelectedfractions);
var selection = new Selection<FractionViewModel>(added, removed, _currentKeyboardKeyPressed);
_oldSelectedfractions.Clear();
foreach (var currentlySelectedfraction in _currentlySelectedfractions)
{
_oldSelectedfractions.Add(currentlySelectedfraction);
}
var selectionChangedArgs = new ItemSelectionChangedEventArgs(selection);
FractionSelectionChanged(this, selectionChangedArgs);
}
}
}
}
Selection.cs
public sealed class Selection<T>
{
private readonly List<T> _added;
private readonly List<T> _removed;
private readonly PressedKeyboardKey _currentKeyboardKeyPressed;
public Selection()
{
_added = new List<T>();
_removed = new List<T>();
}
/// <summary>
/// Initializes a new instance of the <see cref="Selection{T}" /> class.
/// </summary>
/// <param name="addedItems">[NotNull]</param>
/// <param name="removedItems">[NotNull]</param>
/// <param name="currentKeyboardKeyPressed">The current keyboard key pressed.</param>
public Selection(IEnumerable<T> addedItems, IEnumerable<T> removedItems, PressedKeyboardKey currentKeyboardKeyPressed)
: this()
{
_added.AddRange(addedItems);
_removed.AddRange(removedItems);
_currentKeyboardKeyPressed = currentKeyboardKeyPressed;
}
public IReadOnlyList<T> Added
{
get { return _added; }
}
public IReadOnlyList<T> Removed
{
get { return _removed; }
}
public PressedKeyboardKey PressedKeyOnSelection
{
get { return _currentKeyboardKeyPressed; }
}
}
KeyboardKeyPressedEventArgs.cs
public sealed class KeyboardKeyPressedEventArgs : EventArgs
{
public KeyboardKeyPressedEventArgs(PressedKeyboardKey currentKeyboardKeyPressed)
{
CurrentKeyboardKeyPressed = currentKeyboardKeyPressed;
}
public PressedKeyboardKey CurrentKeyboardKeyPressed { get; private set; }
}
ItemSelectionChangedEventArgs.cs
public sealed class ItemSelectionChangedEventArgs : EventArgs
{
public ItemSelectionChangedEventArgs(Selection<FractionViewModel> newSelection)
{
Selection = newSelection;
}
public Selection<FractionViewModel> Selection { get; private set; }
}
PressedKeyboardKey.cs
public enum PressedKeyboardKey
{
None,
Ctrl,
Shift
}

creating a customer user control in wpf and synchronizing with selected item

OK - So I almost have this working. I just need to know who to get the usercontrol to let the viewmodel of the consuming view know there has been a change. Check this out - here is xaml from the consuming view.
<StackPanel>
<pfControls:TriChoiceUserControl Text="{Binding Path=SampleText}" State="{Binding CurrentState}"/>
</StackPanel>
Here is the viewmodel code
class MainWindowViewModel: INotifyPropertyChanged
{
private bool? currentState;
public bool? CurrentState
{
get { return currentState; }
set {
currentState = value;
OnPropertyChanged("CurrentState");
}
}
public string SampleText { get { return "Hi there"; } }
public MainWindowViewModel()
{
CurrentState = false;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Now on the initial load of the ViewModel you can see that Current state is false and indeed the control I ends up with the false check box checked (there are three check boxes, one for yes, one for no and one for na - don't ask me, that is what they told me to do). Problem is that when I check the first one (true in this case) the user control is working in that it goes and unchecks the false check box but and changes the state property but my viewmodel for the consuming view never gets notified. I feel like I am so close... Here is the code for the user control.
public partial class TriChoiceUserControl : UserControl, INotifyPropertyChanged
{
#region Fields (5) 
public static readonly DependencyProperty StateProperty = DependencyProperty.Register("State", typeof(bool?), typeof(TriChoiceUserControl),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeState)));
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(String), typeof(TriChoiceUserControl),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeText)));
#endregion Fields 
public TriChoiceUserControl()
{
InitializeComponent();
}
public bool? State
{
get
{
return (bool?)GetValue(StateProperty);
}
set
{
SetValue(StateProperty, value);
NotifyPropertyChanged("State");
}
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
private static void ChangeState(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as TriChoiceUserControl).UpdateCheckState((bool?)e.NewValue);
}
private static void ChangeText(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as TriChoiceUserControl).UpdateText(e.NewValue.ToString());
}
private void UpdateText(string newText)
{
label1.Content = newText;
}
private void UpdateCheckState(bool? newState)
{
if (newState != null)
{
if ((bool)newState)
{
chkYes.IsChecked = true;
chkNo.IsChecked = false;
chkNa.IsChecked = false;
}
else
{
chkYes.IsChecked = false;
chkNo.IsChecked = true;
chkNa.IsChecked = false;
}
}
else
{
chkYes.IsChecked = false;
chkNo.IsChecked = false;
chkNa.IsChecked = true;
}
State = newState;
}
private void chkYes_Checked(object sender, RoutedEventArgs e)
{
UpdateCheckState(true);
}
private void chkNo_Checked(object sender, RoutedEventArgs e)
{
UpdateCheckState(false);
}
private void chkNa_Checked(object sender, RoutedEventArgs e)
{
UpdateCheckState(null);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Here is the XAML for the user control.
Thanks for any input.
All of this works just fine, I had lost sight of the fact that the default mode is "oneWay" on the binding - duh - I set Mode=TwoWay and no everything works. But that OK, I don't mind saying duh, it usually means I have found the answer :)

Resources