How to add UIContextMenuInteraction to button on Xamarin Forms? - c#

I need to add a context menu to a button for Xamarin iOS.
I found an example on the apple developer site.
So I need to do quite a similar context menu but it should be anchored to the button element.
I created control and named it InteractionButton
public class InteractionButton : Button
{
public static readonly BindableProperty ButtonsListProperty = BindableProperty.Create(
nameof(ButtonsList), typeof(List<string>), typeof(InteractionButton));
public List<string> ItemsList
{
get => (List<string>) GetValue(ButtonsListProperty);
set => SetValue(ButtonsListProperty, value);
}
}
Also, I created a simple iOS renderer for the control -
[assembly: ExportRenderer(typeof(InteractionButton), typeof(InteractionButtonRenderer))]
public class InteractionButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.NewElement is InteractionButton button)
{
// create context menu from button.ItemsList
// NativeView.AddInteraction(new UIContextMenuInteraction());
}
}
}
The problem is that I don't know how should I build UIContextMenuInteraction. We should pass an instance of type IUIContextMenuInteractionDelegate into the UIContextMenuInteraction object.
I could not find any examples of how to do it. All that I have is an example from the apple developer site:
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "plus"), for: .normal)
let barButton = UIBarButtonItem(customView: button)
self.navigationItem.rightBarButtonItem = barButton
let interaction = UIContextMenuInteraction(delegate: self)
button.addInteraction(interaction)
extension ViewController: UIContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint)
-> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in
let importAction = UIAction(title: "Import", image: UIImage(systemName: "folder")) { action in }
let createAction = UIAction(title: "Create", image: UIImage(systemName: "square.and.pencil")) { action in }
return UIMenu(title: "", children: [importAction, createAction])
}
}
}
Is there a simple way of adding context menu interaction to my native control?

I test with the following code it works fine , just long click on the button it will shows the menu .
InteractionButton
public class InteractionButton : Button
{
public static readonly BindableProperty ItemsListProperty = BindableProperty.Create(
nameof(ItemsList),
typeof(List<string>),
typeof(InteractionButton));
public List<string> ItemsList
{
get => (List<string>)GetValue(ItemsListProperty);
set => SetValue(ItemsListProperty, value);
}
}
Xaml
<local:InteractionButton HeightRequest="100" WidthRequest="100" Text="Click on me" ItemsList="{Binding list}"/>
Custom renderer
[assembly: ExportRenderer(typeof(InteractionButton), typeof(MyRenderer))]
namespace FormsApp.iOS
{
class MyRenderer : ButtonRenderer , IUIContextMenuInteractionDelegate
{
public UIContextMenuConfiguration GetConfigurationForMenu(UIContextMenuInteraction interaction, CGPoint location)
{
List<UIMenuElement> elementList = new List<UIMenuElement>();
foreach (var item in (Element as InteractionButton).ItemsList) {
UIAction action = UIAction.Create(item, UIImage.FromFile("dog.png"), item, (a) => {
Console.WriteLine("Click on action1");
});
elementList.Add(action);
}
UIMenu menu = UIMenu.Create("Menu", elementList.ToArray());
return UIContextMenuConfiguration.Create(null,null,(suggestedActions) => menu);
}
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var interaction = new UIContextMenuInteraction(this);
Control.AddInteraction(interaction);
}
}
}
}

Related

WPF Button shortcut key using Dependency Property

I am using a set of standard buttons, reused across a WPF app and want to add a shortcut key to each of the buttons.
So I have a ControlTemplate containing the buttons with Command bound to standard commands
I am trying to add a KeyBinding to the UserControl that contains the Button by adding Dependency Property to the Button and navigating up the Parent tree to the nearest UserControl.
I can get the DP values to set Key and Modifiers from the template Button, but cannot get hold of the Command (maybe because it is not bound until later??)
Any ideas how I can:
Either get hold of or create the Command from the Template per this approach
Or get the Command when it is resolved and then set the KeyBinding
PS: I have set the Key and Modifiers in separate DPs but would prefer to have a single DP of KeyBinding, then set ShortcutBinding.Key and ShortcutBinding.Modifers in XAML.
Is there a way to set the properties of a DP class in XAML like that?
Extract of the XAML of from the button group template:
<ctrl:ButtonShortcut
x:Name="btnUpdate"
Style="{StaticResource EditButtonStyle}"
Command="{Binding DataContext.UpdateCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Content="Update"
ShortcutKey="U"
ShortcutModifiers="Ctrl"/>
The DP class, inherited from Button, implementing the Key and Modifiers to link with the same Command that is bound to the Button:
public partial class ButtonShortcut : Button
{
public KeyBinding ShortcutBinding { get; set; }
public Key ShortcutKey
{
get { return (Key)GetValue(ShortcutKeyProperty); }
set { SetValue(ShortcutKeyProperty, value); }
}
public static readonly DependencyProperty ShortcutKeyProperty =
DependencyProperty.Register("ShortcutKey", typeof(Key), typeof(ButtonShortcut), new PropertyMetadata(Key.None, ShortcutKeyChanged));
public ModifierKeys ShortcutModifiers
{
get { return (ModifierKeys)GetValue(ShortcutModifiersProperty); }
set { SetValue(ShortcutModifiersProperty, value); }
}
public static readonly DependencyProperty ShortcutModifiersProperty =
DependencyProperty.Register("ShortcutModifiers", typeof(ModifierKeys), typeof(ButtonShortcut), new PropertyMetadata(ModifierKeys.None, ShortcutKeyChanged));
private static void ShortcutKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var btn = d as ButtonShortcut;
if (btn != null)
{
FrameworkElement uc = btn.Parent as FrameworkElement;
while (uc?.Parent != null && uc is not UserControl)
uc = uc.Parent as FrameworkElement;
if (btn.ShortcutBinding == null)
{
btn.ShortcutBinding = new KeyBinding();
}
var bindings = btn.CommandBindings;
if (e.NewValue is Key)
btn.ShortcutBinding.Key = (Key)e.NewValue;
if (e.NewValue is ModifierKeys)
btn.ShortcutBinding.Modifiers = (ModifierKeys)e.NewValue;
//So far, so good, but I cannot find the Command to apply to the KeyBinding
btn.ShortcutBinding.Command = btn.Command;
btn.ShortcutBinding.CommandParameter = btn.CommandParameter;
if (btn.Command == null)
System.Diagnostics.Debug.Print("not in Commmand");
if (btn.CommandBindings.Count == 0)
System.Diagnostics.Debug.Print("not in CommandBindings");
if (btn.ReadLocalValue(CommandProperty) == DependencyProperty.UnsetValue)
System.Diagnostics.Debug.Print("not in DP CommandProperty");
if (btn.ShortcutBinding.Key != Key.None && uc != null)
{
if (!uc.InputBindings.Contains(btn.ShortcutBinding))
uc.InputBindings.Add(btn.ShortcutBinding);
}
}
}
public ButtonShortcut()
{
InitializeComponent();
}
}
This looks like a very bad smelling design. The child control should not configure the parent control. Especially not to add "features" that the child pretends to offer: in your case the button is not able to execute key gestures - it only defines them.
Since the parent control is the target of the gestures, it must define the required handlers to execute the gesture. This means, all the action and responsibilities are in the parent UserControl and not in the Button (or child element). Classes should never know details of other classes. A button should never know which parent will execute its command and how to register the command in the first place.
The button is just a passive source. It does not handle commands or gestures itself. Therefore, it would never register a CommandBinding or InputBinding.
Usually, you bind the Button.Command to a command defined on the command target. The button invokes this command on the target (or raises a RoutedCommand) and the target executes a corresponding operation.
It doesn't make sense to define commands or key gestures on a button. The button won't execute them. Commands, key and mouse gestures must be defined on the target, where the related responsibilities are.
You can let the parent UserControl (which I assume in this example is the command target) define a RoutedCommand. Register the corresponding key gesture(s) with this command:
Solution 1
MyUserControl.xaml.cs
partial class MyUserControl : UserControl
{
public static RoutedCommand DoActionCommand { get; }
static MyUserControl()
{
var gestures = new InputGestureCollection
{
new KeyGesture(Key.U, ModifierKeys.Control),
};
DoActionCommand = new RoutedUICommand(
"Do something",
nameof(DoActionCommand),
typeof(CommandTargetUserControl),
gestures);
}
public MyUserControl()
{
InitializeComponent();
this.CommandBindings.Add(new CommandBinding(DoActionCommand, ExecuteDoActionCommand));
}
}
CommandTargetUserControl.xaml
<MyUserControl>
<Button Command="{x:Static local:CommandTargetUserControl.DoActionCommand}" />
</MyUserControl>
Solution 2
To allow configuring default key gestures without modifying the source and the target, you can implement an attached behavior. This behavior basically delegates a hooked command, which is invoked by a key gesture, to the actual command sources that are registered with the particular key gesture. Command source and command target are totally decoupled. Input bindings are configured explicitly to respect encapsulation (no silent and unexpected modidifications).
Usage example
<Window local:KeyGestureDelegate.IsKeyGestureDelegationEnabled="True">
<local:KeyGestureDelegate.TargetKeyGestures>
<InputGestureCollection>
<KeyGesture>Ctrl+U</KeyGesture>
<KeyGesture>Ctrl+Shift+M</KeyGesture>
</InputGestureCollection>
</local:KeyGestureDelegate.TargetKeyGestures>
<StackPanel>
<Button Command="{Binding SomeCommand}"
local:KeyGestureDelegate.SourceKeyGesture="Ctrl+U"
local:KeyGestureDelegate.IsKeyGestureCommandExecutionEnabled="True" />
<Button Command="{Binding SomeOtherCommand}"
local:KeyGestureDelegate.SourceKeyGesture="Shift+Ctrl+M"
local:KeyGestureDelegate.IsKeyGestureCommandExecutionEnabled="True" />
</StackPanel>
</Window>
Implementation example
KeyGestureDelegate.cs
An implementation of the RelayCommand used in this example can be found at Microsoft Docs: Relaying Command Logic.
public class KeyGestureDelegate : DependencyObject
{
// Custom KeyGesture comparer for the Dictionary
private class KeyGestureComparer : EqualityComparer<KeyGesture>
{
public override bool Equals(KeyGesture? x, KeyGesture? y)
=> (x?.Key, x?.Modifiers).Equals((y?.Key, y?.Modifiers));
public override int GetHashCode([DisallowNull] KeyGesture obj)
=> HashCode.Combine(obj.Key, obj.Modifiers);
}
private static ICommand KeyGestureDelegateCommand { get; } = new RelayCommand(ExecuteKeyGestureDelegateCommand);
public static bool GetIsKeyGestureDelegationEnabled(DependencyObject attachedElement) => (bool)attachedElement.GetValue(IsKeyGestureDelegationEnabledProperty);
public static void SetIsKeyGestureDelegationEnabled(DependencyObject attachedElement, bool value) => attachedElement.SetValue(IsKeyGestureDelegationEnabledProperty, value);
public static readonly DependencyProperty IsKeyGestureDelegationEnabledProperty = DependencyProperty.RegisterAttached(
"IsKeyGestureDelegationEnabled",
typeof(bool),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(bool), OnIsKeyGestureDelegationEnabled));
public static bool GetIsKeyGestureCommandExecutionEnabled(DependencyObject attachedElement) => (bool)attachedElement.GetValue(IsKeyGestureCommandExecutionEnabledProperty);
public static void SetIsKeyGestureCommandExecutionEnabled(DependencyObject attachedElement, bool value) => attachedElement.SetValue(IsKeyGestureCommandExecutionEnabledProperty, value);
public static readonly DependencyProperty IsKeyGestureCommandExecutionEnabledProperty = DependencyProperty.RegisterAttached(
"IsKeyGestureCommandExecutionEnabled",
typeof(bool),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(bool), OnIsKeyGestureCommandExecutionEnabled));
public static InputGestureCollection GetTargetKeyGestures(DependencyObject obj) => (InputGestureCollection)obj.GetValue(TargetKeyGesturesProperty);
public static void SetTargetKeyGestures(DependencyObject obj, InputGestureCollection value) => obj.SetValue(TargetKeyGesturesProperty, value);
public static readonly DependencyProperty TargetKeyGesturesProperty = DependencyProperty.RegisterAttached(
"TargetKeyGestures",
typeof(InputGestureCollection),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(InputGestureCollection), OnTargetKeyGesturesChanged));
public static KeyGesture GetSourceKeyGesture(DependencyObject attachedElement) => (KeyGesture)attachedElement.GetValue(SourceKeyGestureProperty);
public static void SetSourceKeyGesture(DependencyObject attachedElement, KeyGesture value) => attachedElement.SetValue(SourceKeyGestureProperty, value);
public static readonly DependencyProperty SourceKeyGestureProperty = DependencyProperty.RegisterAttached(
"SourceKeyGesture",
typeof(KeyGesture),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(KeyGesture), OnSourceKeyGestureChanged));
// Remember added InputBindings to enable later removal
private static Dictionary<UIElement, IList<InputBinding>> InputBindingTargetMap { get; } = new Dictionary<UIElement, IList<InputBinding>>();
// Lookup command sources that map to a particular gesture
private static Dictionary<KeyGesture, IList<ICommandSource>> InputBindingSourceMap { get; } = new Dictionary<KeyGesture, IList<ICommandSource>>(new KeyGestureComparer());
private static void OnIsKeyGestureDelegationEnabled(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not UIElement keyGestureHandler)
{
throw new ArgumentException($"Attached element must be of type {typeof(UIElement)}.");
}
InputGestureCollection gestures = GetTargetKeyGestures(keyGestureHandler);
if ((bool)e.NewValue)
{
RegisterKeyBinding(keyGestureHandler, gestures);
}
else
{
UnregisterKeyBinding(keyGestureHandler);
}
}
private static void OnIsKeyGestureCommandExecutionEnabled(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not ICommandSource commandSource)
{
throw new ArgumentException($"Attached element must be of type {typeof(ICommandSource)}.");
}
KeyGesture keyGesture = GetSourceKeyGesture(attachedElement);
if ((bool)e.NewValue)
{
RegisterCommandBinding(commandSource, keyGesture);
}
else
{
UnregisterCommandBinding(commandSource, keyGesture);
}
}
private static void OnTargetKeyGesturesChanged(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not UIElement keyGestureHandler)
{
throw new ArgumentException($"Attached element must be of type {typeof(UIElement)}.");
}
if (e.OldValue is InputBindingCollection)
{
UnregisterKeyBinding(keyGestureHandler);
}
if (!GetIsKeyGestureDelegationEnabled(keyGestureHandler))
{
return;
}
RegisterKeyBinding(keyGestureHandler, e.NewValue as InputGestureCollection);
}
private static void OnSourceKeyGestureChanged(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not ICommandSource commandSource)
{
throw new ArgumentException($"Attached element must be of type {typeof(ICommandSource)}.");
}
UnregisterCommandBinding(commandSource, e.OldValue as KeyGesture);
if (!GetIsKeyGestureCommandExecutionEnabled(attachedElement))
{
return;
}
RegisterCommandBinding(commandSource, e.NewValue as KeyGesture);
}
private static void ExecuteKeyGestureDelegateCommand(object commandParameter)
{
if (InputBindingSourceMap.TryGetValue(commandParameter as KeyGesture, out IList<ICommandSource> commandSources))
{
foreach (ICommandSource commandSource in commandSources)
{
ExecuteCommandSource(commandSource);
}
}
}
private static void ExecuteCommandSource(ICommandSource commandSource)
{
if (commandSource.Command is RoutedCommand routedCommand)
{
IInputElement commandTarget = commandSource.CommandTarget ?? commandSource as IInputElement;
if (routedCommand.CanExecute(commandSource.CommandParameter, commandTarget))
{
routedCommand.Execute(commandSource.CommandParameter, commandTarget);
}
}
else if (commandSource.Command?.CanExecute(parameter: commandSource.CommandParameter) ?? false)
{
commandSource.Command.Execute(commandSource.CommandParameter);
}
}
private static void RegisterKeyBinding(UIElement keyGestureHandler, InputGestureCollection inputGestureCollection)
{
if (inputGestureCollection == null)
{
return;
}
IList<InputBinding>? inputBindings = new List<InputBinding>();
InputBindingTargetMap.Add(keyGestureHandler, inputBindings);
foreach (KeyGesture gesture in inputGestureCollection.OfType<KeyGesture>())
{
var inputBinding = new KeyBinding(KeyGestureDelegateCommand, gesture) { CommandParameter = gesture };
keyGestureHandler.InputBindings.Add(inputBinding);
inputBindings.Add(inputBinding);
}
}
private static void UnregisterKeyBinding(UIElement keyGestureHandler)
{
if (InputBindingTargetMap.TryGetValue(keyGestureHandler, out IList<InputBinding>? inputBindings))
{
foreach (InputBinding inputBinding in inputBindings)
{
keyGestureHandler.InputBindings.Remove(inputBinding);
}
InputBindingTargetMap.Remove(keyGestureHandler);
}
}
private static void RegisterCommandBinding(ICommandSource commandSource, KeyGesture keyGesture)
{
if (keyGesture == null)
{
return;
}
if (!InputBindingSourceMap.TryGetValue(keyGesture, out IList<ICommandSource>? commandSources))
{
commandSources = new List<ICommandSource>();
InputBindingSourceMap.Add(keyGesture, commandSources);
}
commandSources.Add(commandSource);
}
private static void UnregisterCommandBinding(ICommandSource commandSource, KeyGesture keyGesture)
{
if (keyGesture == null)
{
return;
}
if (InputBindingSourceMap.TryGetValue(keyGesture, out IList<ICommandSource>? commandSources))
{
commandSources.Remove(commandSource);
if (!commandSources.Any())
{
InputBindingSourceMap.Remove(keyGesture);
}
}
}
}

How to prevent Xamarin forms, a custom menu and WebView renderer with EvaluateJavascript from freezing?

I have created a custom menu item which appears in the default menu which pops up when selecting text on my custom WebView.
On clicking on the menu item it calls EvaluateJavascript to get the selected WebView text, and then passes the text to another page.
However after performing this action once or twice, some text on certain areas of the screen start to become unresponsive to clicks eg. text on the parts of the WebView become unselectable, clicks on that part of the screen on other pages becomes unresponsive and even the soft keyboard becomes unclickable in some spots. If this continues for a while sometimes my app will then suddenly freeze the entire operating system and I have to soft reset my phone. It appears that there maybe some serious memory leakage going on.
I create my custom menu item in the MainActivity class:
public override void OnActionModeStarted(ActionMode mode)
{
if (Root.IsCurrentPageType<DictPage>() && DictP.IsWebViewFocused())
{
IMenu menu = mode.Menu;
menu.Add("To Notes");
menu.GetItem(0).SetOnMenuItemClickListener(new MyMenuItemOnMenuItemClickListener(this, mode));
}
base.OnActionModeStarted(mode);
}
It is then handled in the Listener class...
public class MyMenuItemOnMenuItemClickListener : Java.Lang.Object, IMenuItemOnMenuItemClickListener
{
private MainActivity mContext;
ActionMode _mode;
public MyMenuItemOnMenuItemClickListener(MainActivity activity, ActionMode mode)
{
this.mContext = activity;
_mode = mode;
}
public bool OnMenuItemClick(IMenuItem item)
{
WEB.CopyToMainNotes();
Device.BeginInvokeOnMainThread(() =>
{
//close menu if clicked
_mode?.Finish();
});
return true;
}
}
...which calls CopyToMainNotes on my derived WebView class and its associated Renderer and EventHandler classes:
public class WebViewEx : Xamarin.Forms.WebView
{
public static WebViewEx WEB;
//Namespace
//YourClass
public event WebViewExEventHandler CallNativeMethodEvent;
public void CallNativeMethod(WebViewExEventType type)
{
WebViewExEventArgs e = new WebViewExEventArgs();
e.EventType = type;
CallNativeMethodEvent?.Invoke(this, e);
}
public WebViewEx()
{
WEB = this;
}
public void CopyToMainNotes()
{
Device.BeginInvokeOnMainThread(() =>
{
CallNativeMethod(WebViewExEventType.copyToMainNotes);
});
}
}
public delegate void WebViewExEventHandler(object sender, WebViewExEventArgs e);
public class WebViewExEventArgs : EventArgs
{
public enum WebViewExEventType { copyToMainNotes };
public WebViewExEventType EventType = WebViewExEventType.copyToMainNotes;
public WebViewExEventArgs() : base()
{
}
}
public class WebViewExRenderer : WebViewRenderer
{
public WebViewExRenderer(Android.Content.Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
WebViewEx ex = e.NewElement as WebViewEx;
ex.CallNativeMethodEvent += WebViewEx_CallNativeMethodEvent;
}
}
internal class JavascriptCallback : Java.Lang.Object, IValueCallback
{
public JavascriptCallback(Action<string> callback)
{
_callback = callback;
}
private Action<string> _callback;
public void OnReceiveValue(Java.Lang.Object value)
{
_callback?.Invoke(Convert.ToString(value));
}
}
private void WebViewEx_CallNativeMethodEvent(object sender, WebViewExEventArgs e)
{
switch (e.EventType)
{
case WebViewExEventType.copyToMainNotes:
{
CopyToMainNotes();
break;
}
}
}
public void CopyToMainNotes()
{
string script = "(function(){ return window.getSelection().toString()})()";
var response = string.Empty;
Control?.EvaluateJavascript(script, new JavascriptCallback((r) =>
{
response = r;
Device.BeginInvokeOnMainThread(() =>
{
DPage.CopyThisTextToAnotherPage(response.ToString().Trim('\"'));
});
}));
}
}
The CopyToMainNotes method above is where the EvaluateJavascript takes place and the selected text finally gets sent to another page.
Any ideas where I might be going wrong here? Thanks in advance!

How to access method from view inside a Xamarin Forms custom renderer?

I have the following code:
public partial class PhrasesFrameRendererClass : Frame
{
.....
void getRandomWords() {
// more code here that involves getting random numbers
// and updating a grid's bindingcontext
}
}
In my custom renderer I want to be able to call the getRandomWords on swipe left gesture like below:
public class PhraseFrameCustomRenderer : FrameRenderer
{
UISwipeGestureRecognizer leftSwipeGestureRecognizer;
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
leftSwipeGestureRecognizer = new UISwipeGestureRecognizer();
leftSwipeGestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Left;
leftSwipeGestureRecognizer.NumberOfTouchesRequired = 1;
leftSwipeGestureRecognizer.AddTarget((obj) =>
{
// Call getRandomWords() here
});
}
}
Is this possible? Any ideas on how this could be done?
base.OnElementChanged(e);
leftSwipeGestureRecognizer = new UISwipeGestureRecognizer();
leftSwipeGestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Left;
leftSwipeGestureRecognizer.NumberOfTouchesRequired = 1;
leftSwipeGestureRecognizer.AddTarget((obj) =>
{
// Call getRandomWords() here
var frame = Element as PhrasesFrameRendererClass ;
if(frame!=null){
frame.getRandomWords();
}
});
You can create a BindableProperty of type Command in your custom frame class, call that Command from your renderer and bind your ViewModel's getRandomWords method as a Command
//Your custom control in your PCL project
public partial class PhrasesFrameRendererClass : Frame
{
public static readonly BindableProperty SwipeLeftCommandProperty =
BindableProperty.Create(nameof(SwipeLeftCommand), typeof(ICommand), typeof(PhrasesFrameRendererClass ), null);
public ICommand SwipeLeftCommand
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
}
//Your custom control renderer
public class PhraseFrameCustomRenderer : FrameRenderer
{
UISwipeGestureRecognizer leftSwipeGestureRecognizer;
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
leftSwipeGestureRecognizer = new UISwipeGestureRecognizer();
leftSwipeGestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Left;
leftSwipeGestureRecognizer.NumberOfTouchesRequired = 1;
leftSwipeGestureRecognizer.AddTarget((obj) =>
{
var myFrame = Element as PhrasesFrameRendererClassl
if(myFrame != null){
if(myFrame.SwipeLeftCommand != null && myFrame.SwipeLeftCommand.CanExecute()){
myFrame.SwipeLeftCommand.Execute();
}
}
});
}
}
//Your ViewModel
public class PhrasesViewModel{
public Command GetRandomWordsCommand {get;set;}
public PhrasesViewModel(){
GetRandomWordsCommand = new Command(ExecuteGetRandomWords);
}
private void ExecuteGetRandomWords(){
//Your method goes here
}
}
//Your XAML
<yourControls:PhrasesFrameRendererClass SwipeLeftCommand="{Binding GetRandomWordsCommand }"/>
It may seem more complicated this way, but using commands allows you to separate your application code (Such as getting random phrases) from your rendering code

TextBlock Inline Binding using DependencyProperty [duplicate]

This question already has answers here:
DependencyProperty getter/setter not being called
(2 answers)
Closed 7 years ago.
I'm using MVVMLight. This is what I have from an example here.
private ObservableCollection<Inline> _inlineList;
public ObservableCollection<Inline> InlineList
{
get { return _inlineList; }
set { Set(() => InlineList, ref _inlineList, value); }
}
private void SendClicked()
{
InlineList.Add(new Run("This is some bold text") { FontWeight = FontWeights.Bold });
InlineList.Add(new Run("Some more text"));
InlineList.Add(new Run("This is some text") { TextDecorations = TextDecorations.Underline });
}
public class BindableTextBlock : TextBlock
{
public ObservableCollection<Inline> InlineList
{
get { return (ObservableCollection<Inline>)GetValue(InlineListProperty); }
set { SetValue(InlineListProperty, value); }
}
public static readonly DependencyProperty InlineListProperty =
DependencyProperty.Register("InlineList", typeof(ObservableCollection<Inline>), typeof(BindableTextBlock), new UIPropertyMetadata(null, OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null) return;
var textBlock = (BindableTextBlock)sender;
textBlock.Inlines.AddRange((ObservableCollection<Inline>)e.NewValue);
}
}
<testRobot:BindableTextBlock
Width="Auto" Height="Auto"
InlineList="{Binding InlineList}" >
</testRobot:BindableTextBlock>
The problem is that bound property InlineList never gets updated. I don't see any text I add to the collection ObservableCollection. When I put a break point in OnPropertyChanged method it never gets hit. I know my data context is set correctly as other bound controls work.
What could be the problem?
Ok you only need to add these in your BindableTextBlock With your Original solution. What we do here is we add handler for when collection is changed (meaning new values are added), we only do that when the collection is set. So with the binding that you have in your xaml every change you make on the collection in the VM fires collection changed event in the textblock which in turn just appends values to the Inline.
private void CollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e)
{
Inlines.AddRange(e.NewItems);
}
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null) return;
var textBlock = (BindableTextBlock)sender;
textBlock.InlineList.CollectionChanged += textBlock.CollectionChangedHandler;
}
BEFORE EDIT for history reasons
Ok I saw what's happening so first the explanation then an example.
So first some basic concepts about wpf:
In order to have your view notified for a change in bound variable in your ViewModel (or whatever that is DataContext at the moment) you have to either RaisePropertyChanged event with the name of the changed property or use something that's doing this somehow :) - like ObservableCollection.
So some use cases:
You have a property with field -> it is common practice to have it like this:
private ICollection<Inline> _inlineList;
public ICollection<Inline> InlineList
{
get
{
return _inlineList;
}
set
{
_inlineList = value;
RaisePropertyChanged("InlineList");
}
}
This ensures that when you set a new value to InlineList the view will be notified
Or in your case what I've used:
private ICollection<Inline> _inlineList;
public ICollection<Inline> InlineList
{
get { return _inlineList; }
set { Set(() => InlineList, ref _inlineList, value); }
}
If you check the description of Set method you'll see that it is setting the value and raising the property (and some more stuff)
You want to have automatic updates and use ObservableCollection -> I use it like this:
private ObservableCollection<ClientFilter> clientFilters;
public IEnumerable<ClientFilter> ClientFilters
{
get
{
if (this.clientFilters == null)
{
this.clientFilters = new ObservableCollection<ClientFilter>();
}
return this.clientFilters;
}
set
{
if (this.clientFilters == null)
{
this.clientFilters = new ObservableCollection<ClientFilter>();
}
SetObservableValues<ClientFilter>(this.clientFilters, value);
}
}
The method SetObservableValues is in my main ViewModel and is doing this:
public static void SetObservableValues<T>(
ObservableCollection<T> observableCollection,
IEnumerable<T> values)
{
if (observableCollection != values)
{
observableCollection.Clear();
foreach (var item in values)
{
observableCollection.Add(item);
}
}
}
This method ensures that if the reference to the obs collection is not the same it will clear the old one and reuse it, because when you bind you bind to the reference at common mistake is to then change the reference itself not the values, which in turn doesn't update anything on the UI and you think binding is broken :)
So If you want it to function normally you just add/remove to the Collection/Enumerable ClientFilters
Now the solution
So I'm not 100% sure what you want to achieve but here's what you could do in order to have your binding working
Your ViewModel
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
namespace WpfApplication3.ViewModel
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
}
private ICollection<Inline> _inlineList;
public ICollection<Inline> InlineList
{
get { return _inlineList; }
set { Set(() => InlineList, ref _inlineList, value); }
}
public RelayCommand SendClicked
{
get
{
return new RelayCommand(() =>
{
InlineList = new List<Inline>
{
new Run("This is some bold text") { FontWeight = FontWeights.Bold },
new Run("Some more text"),
new Run("This is some text") { TextDecorations = TextDecorations.Underline }
};
});
}
}
}
}
Your custom control -> BindableTextBlock
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
namespace WpfApplication3
{
public class BindableTextBlock : TextBlock
{
public ICollection<Inline> InlineList
{
get { return (ICollection<Inline>)GetValue(InlineListProperty); }
set { SetValue(InlineListProperty, value); }
}
public static readonly DependencyProperty InlineListProperty =
DependencyProperty.Register("InlineList", typeof(List<Inline>), typeof(BindableTextBlock), new UIPropertyMetadata(null, OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null) return;
var textBlock = (BindableTextBlock)sender;
textBlock.Inlines.AddRange((ICollection<Inline>)e.NewValue);
}
}
}
Your XAML
On your Page or Window (depending on platform)
DataContext="{Binding Main, Source={StaticResource Locator}}"
Then inside
<StackPanel>
<Button Command="{Binding SendClicked}">SendClicked</Button>
<local:BindableTextBlock Background="Black" Foreground="AliceBlue"
Width = "Auto" Height="Auto"
InlineList="{Binding InlineList}"
>
</local:BindableTextBlock>
</StackPanel>
All assuming you have your ViewModelLocator from MVVM Light register your view model
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
namespace WpfApplication3.ViewModel
{
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public static void Cleanup()
{
}
}
}
ALTERNATIVE
Alternatively you could have your command like this:
public RelayCommand SendClicked
{
get
{
return new RelayCommand(() =>
{
_inlineList = new List<Inline>();
InlineList.Add(new Run("This is some bold text") { FontWeight = FontWeights.Bold });
InlineList.Add(new Run("Some more text"));
InlineList.Add(new Run("This is some text") { TextDecorations = TextDecorations.Underline });
RaisePropertyChanged("InlineList");
});
}
}
But you have to use the other option of defining the property as described at the beginning of my post.
You could do it in other ways of course.
Just one more advice -> It is considered bad practice and not in the spirit of MVVM to have UI elements in your view model, so change in architecture is strongly advised in this code IMO.
Post got too long (as usual), if you need aditional explanation please let me know.
Cheers and happy coding ;)

Creating Smart Tag for Form (not other control) using c#

As the Form of System.Windows.Forms inherits from Control, I was wondering if there is a way to create a Custom Form and its Designer with some options (shortcuts) to create a title or somthings like that.
I tried this, but nothings happend, the Form I calles ManagedForm
[Designer(typeof(ManagedFormDesigner))]
public class ManagedForm : Form{
//code here
}
[PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
public class ManagedFormDesigner : ControlDesigner {
private DesignerActionListCollection actionLists;
public override DesignerActionListCollection ActionLists {
get {
if (actionLists == null) {
actionLists = new DesignerActionListCollection();
actionLists.Add(new ManagedFormDesignerActionList(this.Component));
}
return actionLists;
}
}
}
public class ManagedFormDesignerActionList : DesignerActionList {
private ManagedForm managedForm = null;
private DesignerActionUIService designerActionUISvc = null;
public ManagedFormDesignerActionList(IComponent component) : base(component) {
this.managedForm = component as ManagedForm;
this.designerActionUISvc =
GetService(typeof(DesignerActionUIService))
as DesignerActionUIService;
}
public override DesignerActionItemCollection GetSortedActionItems() {
DesignerActionItemCollection items = new DesignerActionItemCollection();
items.Add(new DesignerActionMethodItem(this, "CreateTitle", "Create Title", "Appearence", true));
return items;
}
public void CreateTitle() {
Panel pTitulo = new Panel();
pTitulo.Size= new Size(100,25);
pTitulo.Dock = DockStyle.Top;
(this.Component as ManagedForm).Controls.Add(pTitulo);
}
}
Action list are show when you click on the little arrow on the control inside a form (or on a component on the bottom of the designer if the object is a component).
Other things you can do is to manage verbs.
Verbs Handling is implemented on the ControlDesigner class (ManagedFormDesigner in your case).
You can see verbs clicking right mouse button or on the bottom of the properties (i.e. TabControl ha 2 verbs, add tab and remove tab).
You can implement verbs adding to ControlDesigner (or ComponentDesigner) class something like this
private DesignerVerbCollection _verbs;
public override DesignerVerbCollection Verbs
{
get
{
if (_verbs == null)
{
_verbs = new DesignerVerbCollection();
_verbs.Add(new DesignerVerb("Create Title", new EventHandler(MyCreateTitleHandler)));
}
return _verbs;
}
}
private void MyCreateTitleHandler(object sender, EventArgs e)
{
// Do here something but take care to show things via IUIService service
IUIService uiService = GetService(typeof(IUIService)) as IUIService;
}

Categories

Resources