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;
}
}
Related
I created my own DataGrid which implements a RowClick Event.
However while trying to bind a Command to it, it'll throw the exception:
{"The event \"RowClick\" on type \"ExtendedDataGrid\" has an incompatible signature. Make sure the event is public and satisfies the EventHandler delegate."}
Since I am new to MVVM my already hurts from all the Input I got in the last couple days about MVVM..Can someone hint me the (mostly) obvious error?
Thanks in advance
Here's my (testproject) code:
public class ExtendedDataGrid : DataGrid
{
public event EventHandler<DataGridRow> RowClick;
public ExtendedDataGrid()
{
this.DefaultStyleKey = typeof(DataGrid);
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
var row = (DataGridRow)element;
row.PreviewKeyDown += RowOnKeyDown;
row.MouseLeftButtonUp += RowOnMouseLeftButtonUp;
base.PrepareContainerForItemOverride(element, item);
}
protected override void ClearContainerForItemOverride(DependencyObject element, object item)
{
var row = (DataGridRow)element;
row.KeyUp -= RowOnKeyDown;
row.MouseLeftButtonUp -= RowOnMouseLeftButtonUp;
base.ClearContainerForItemOverride(element, item);
}
private void RowOnMouseLeftButtonUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
mouseButtonEventArgs.Handled = true;
this.OnRowClick((DataGridRow)sender);
}
private void RowOnKeyDown(object sender, KeyEventArgs keyEventArgs)
{
if (keyEventArgs.Key != Key.Enter)
return;
keyEventArgs.Handled = true;
this.OnRowClick((DataGridRow)sender);
}
protected virtual void OnRowClick(DataGridRow clickedRow)
{
if (null == this.RowClick)
return;
this.RowClick(this, clickedRow);
}
}
Window.xaml
<controls1:ExtendedDataGrid x:Name="extGrid">
<i:Interaction.Triggers>
<i:EventTrigger EventName="RowClick" SourceObject="{Binding ElementName=extGrid}">
<i:InvokeCommandAction Command="{Binding MyCommand}" CommandParameter="{Binding SelectedItem,ElementName=extGrid}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<controls1:ExtendedDataGrid.Items>
<TextBlock Text="Text" />
</controls1:ExtendedDataGrid.Items>
</controls1:ExtendedDataGrid>
window.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this._selectCommand = new DelegateCommand<DataGridRow>(x =>
{
});
//following works fine..
this.extGrid.RowClick += (s, e) =>
{
};
}
private DelegateCommand<DataGridRow> _selectCommand;
public ICommand MyCommand
{
get
{
return this._selectCommand;
}
}
}
DelegateCommand Implementation:
public class DelegateCommand<T> : DelegateCommand
{
public DelegateCommand(Action<T> executeHandler)
: this(null, executeHandler)
{ }
public DelegateCommand(Func<T, bool> canExecuteHandler, Action<T> executeHandler)
: base(o => null == canExecuteHandler || canExecuteHandler((T)o), o => executeHandler((T)o))
{
if (null == executeHandler)
throw new ArgumentNullException("executeHandler");
}
}
/// <summary>
/// Stellt ein standard DelegateCommand dar.
/// </summary>
public class DelegateCommand : ICommand
{
#region Events
public event EventHandler CanExecuteChanged;
#endregion
#region Variablen
private readonly Action<object> _executeHandler;
private readonly Func<object, bool> _canExecuteHandler;
private bool _isExecuting = false;
#endregion
#region Eigenschaften
public bool IsSingleExecution { get; set; }
#endregion
#region Konstruktor
public DelegateCommand(Action<object> executeHandler)
: this(null, executeHandler)
{ }
public DelegateCommand(Func<object, bool> canExecuteHandler, Action<object> executeHandler)
{
if (null == executeHandler)
throw new ArgumentNullException("executeHandler");
this._executeHandler = executeHandler;
this._canExecuteHandler = canExecuteHandler;
}
#endregion
#region Public Methoden
public virtual bool CanExecute(object parameter)
{
return (!this.IsSingleExecution || (this.IsSingleExecution && !this._isExecuting)) && (null == this._canExecuteHandler || this._canExecuteHandler(parameter));
}
public virtual void Execute(object parameter)
{
if (this.CanExecute(parameter))
{
this._isExecuting = true;
this.RaiseCanExecuteChanged();
try
{
this._executeHandler(parameter);
}
finally
{
this._isExecuting = false;
this.RaiseCanExecuteChanged();
}
}
}
public void RaiseCanExecuteChanged()
{
if (null != CanExecuteChanged)
CanExecuteChanged(this, EventArgs.Empty);
}
#endregion
The problem is coming from this line:
<i:EventTrigger EventName="RowClick" SourceObject="{Binding ElementName=extGrid}">
The EventTrigger class is expecting a routed event which uses the RoutedEventHandler delegate not the EventHandler delegate.
These are the changes you have to make in your code to make it work:
In ExtendedDataGrid:
public class ExtendedDataGrid : DataGrid
{
public static readonly RoutedEvent RowClickEvent = EventManager.RegisterRoutedEvent("RowClick",
RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ExtendedDataGrid));
public event RoutedEventHandler RowClick
{
add { AddHandler(RowClickEvent, value); }
remove { RemoveHandler(RowClickEvent, value); }
}
public ExtendedDataGrid()
{
this.DefaultStyleKey = typeof(DataGrid);
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
var row = (DataGridRow)element;
row.PreviewKeyDown += RowOnKeyDown;
row.MouseLeftButtonUp += RowOnMouseLeftButtonUp;
base.PrepareContainerForItemOverride(element, item);
}
protected override void ClearContainerForItemOverride(DependencyObject element, object item)
{
var row = (DataGridRow)element;
row.KeyUp -= RowOnKeyDown;
row.MouseLeftButtonUp -= RowOnMouseLeftButtonUp;
base.ClearContainerForItemOverride(element, item);
}
private void RowOnMouseLeftButtonUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
mouseButtonEventArgs.Handled = true;
this.OnRowClick((DataGridRow)sender);
}
private void RowOnKeyDown(object sender, KeyEventArgs keyEventArgs)
{
if (keyEventArgs.Key != Key.Enter)
return;
keyEventArgs.Handled = true;
this.OnRowClick((DataGridRow)sender);
}
protected virtual void OnRowClick(DataGridRow clickedRow)
{
var args = new RowClickRoutedEventArgs(clickedRow);
args.RoutedEvent = RowClickEvent;
RaiseEvent(args);
}
}
Here I removed the previous RowClick event and changed the OnRowClick method.
Add a new class called RowClickRoutedEventArgs:
public class RowClickRoutedEventArgs : RoutedEventArgs
{
public RowClickRoutedEventArgs(DataGridRow dataGridRow)
{
Row = dataGridRow;
}
public DataGridRow Row { get; set; }
}
In the following example the temp variable in RaisePropertyChanged() is always null. How do I subscribe to the event?
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Runtime.CompilerServices;
using System.Text;
namespace TestProject.Module.BusinessObjects
{
public class ContactPerson : INotifyPropertyChanged
{
public string FirstName { get; set; }
public string LastName { get; set; }
[NotMapped]
public string PersonFullName
{
set
{
if (PersonFullName != value)
{
var stringArray = value.Split();
var firstIndex = 0;
var lastIndex = stringArray.Length - 1;
if (lastIndex >= firstIndex)
{
FirstName = stringArray[firstIndex];
}
if (lastIndex > firstIndex)
{
LastName = stringArray[lastIndex];
}
RaisePropertyChanged();
}
}
get
{
var sb = new StringBuilder();
sb.Append(FirstName);
sb.Append(" ");
sb.Append(LastName);
var stringArray = sb.ToString().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
var s = string.Join(" ", stringArray);
return s;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ContactPerson Clone()
{
var obj = new ContactPerson { FirstName = FirstName, LastName = LastName };
return obj;
}
public override string ToString()
{
return PersonFullName;
}
protected void RaisePropertyChanged([CallerMemberName] string propertName = "")
{
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(propertName));
}
}
}
}
from reading This question it seems that PropertyChanged has not been subscribed to. How do I do this subscription?
Like this:
private void Form1_Load(object sender, EventArgs e)
{
ContactPerson p = new ContactPerson();
p.PropertyChanged += P_PropertyChanged;
}
private void P_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
throw new NotImplementedException();
}
EDIT: expanding the sample code:
public partial class Form1 : Form
{
ContactPerson p;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
p = new ContactPerson();
p.PersonFullName = "Mary Jane";
p.PropertyChanged += P_PropertyChanged;
label1.Text = p.PersonFullName;
// If you use databinding instead, you get the same result in this case.
//label1.DataBindings.Add(new Binding("Text", p, "PersonFullName"));
}
private void P_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
label1.Text = p.PersonFullName;
}
private void button1_Click(object sender, EventArgs e)
{
p.PersonFullName = "John Doe";
}
}
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
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 :)
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#