Extending WPF Button to store data in a new property - wpf

I want to extend a WPF button to store some extra data, in a similar way to the current "Tag" property. Are attached properties the way forward? The data I want to store will be a URL Link string, for example I would like to be able to do something like:
<Button Tag="BBC World News" URLLink="http://www.bbc.co.uk"/>
Can anyone help me to understand how to extend the Button?
Many thanks
Jay

You can use an attached property:
public class ButtonBehavior
{
public static readonly DependencyProperty UrlLinkProperty =
DependencyProperty.RegisterAttached("UrlLink",
typeof(ButtonBase),
typeof(ButtonBehavior),
new FrameworkPropertyMetadata(null));
public static string GetUrlLink(DependencyObject d)
{
return (string)d.GetValue(UrlLinkProperty);
}
public static void SetUrlLink(DependencyObject d, string value)
{
d.SetValue(UrlLinkProperty, value);
}
}
Then you can declare your button like this:
<Button Tag="BBC World News" ButtonBehavior.UrlLink="http://www.bbc.co.uk" Click="btnArticleView"/>
And you click handler will look like this:
protected void btnArticleView(object sender, RoutedEventArgs e)
{
Button rb = sender as Button;
string TheTitle = rb.Tag.ToString();
string TheURL = ButtonBehavior.GetUrlLink(rb);
// Further code here
}

Define the attached property like this
public static readonly DependencyProperty UrlLinkProperty =
DependencyProperty.RegisterAttached("UrlLink",
typeof(String),
typeof(ButtonBehavior)
new FrameworkPropertyMetadata(null));

I am not sure about your goal here. You dont need to extend the Button for storing two values. You can always set the Datacontext of the Button with custom data object which contains this two values. Can you explain more on your problem.

Thanks for your response.
I have a listbox which has a complex custom data template. The template contains a button. When I click the button I want to send 2 strings which are "URL link" and "URL Title". So I am binding many values in my list box data template, and I want to pass two of them on Button click event. Code example:
protected void btnArticleView(object sender, RoutedEventArgs e)
{
Button rb = sender as Button;
string TheTitle = rb.Tag.ToString();
string TheURL = rb.URLLink.ToString();
// Further code here
}
and XAML:
<Button Tag="BBC World News" URLLink="http://www.bbc.co.uk" Click="btnArticleView"/>
Cheers
Jay

I know this is an old post but...
you could always create a custom class "MyURL" with 2 properties "URLText" and "URLTitle" then create that object "objURL" and set the values and set the button.datacontext = objURL.
Then in the click event difine rb and obj and your text variables
obj = rb.datacontext
theTitle = obj.urltitle
theURL = obj.urltext

Related

How to receive the InkCanvas.StrokeCollected event in the view model

In using MVVM pattern, I have a custom inkcanvas with:
protected override void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e)
{
CustomStroke newStroke = new CustomStroke(e.Stroke.StylusPoints, e.Stroke.DrawingAttributes);
this.Strokes.Remove(e.Stroke);
this.Strokes.Add(newStroke);
InkCanvasStrokeCollectedEventArgs eNew = new InkCanvasStrokeCollectedEventArgs(newStroke);
// Raises the System.Windows.Controls.InkCanvas.StrokeCollected event.
base.OnStrokeCollected(eNew);
}
How do I get the view model to receive the InkCanvas.StrokeCollected event?
I can not bind the XAML to the strokes as the StrokeCollection.CollectionChanged event will be called three times by the custom inkcanvas.
Any help is appreciated.
Try this
public Window3()
{
InitializeComponent();
var vm=new ViewModel();
this.DataContext = vm;
canvas.StrokeCollected += vm.OnStrokeCollected;
}
ViewModel
public class ViewModel
{
public void OnStrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
{
}
}
Edit
if you want to do it without codebehind see the article EventTrigger
You simply bind it via XAML as you already did, which is the correct way to do it.
That you get 3 events, doesn't matter. Just handle the one you need.
For example, if you are only interested in the StrokeCollectedEvent, then just do
protected override void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e)
{
if(e.RoutedEvent != InkCanvas.StrokeCollectedEvent)
return;
// handle the event
}
For a full list of Events, consult the "Fields" Section of InkCanvas MSDN documentation. The fields ending with "Event" are RoutedEvent constants, which are passed in the InkCanvasStrokeCollectedEventArgs.

How to find the ICommand binding target (the control)

We use the MVVM pattern. In the View, I have binding the save command to a button:
In the view model, I would like to find out the save command binding target, does it possible?
private Button GetBindingControl(ICommand command)
{
// What should I do here:
return button;
}
It's not possible, and it defeats the purpose of MVVM (having the UI logic in the VM regardless of the controls used)
Maybe you could ask instead what problem you are trying to solve.
As #Diego said, this defats the purpose of MVVM because we must try hard not to include visuals or controls in the view models in MVVM...
Having said that there are two options...
Using RoutedCommands
Using Attached Behaviors.
RoutedCommands are not readily allowed in MVVM as they need to be closely command bound to the UI element i.e. in our case the Button. Hence they too defeat the purpose of MVVM.
But MVVM happily co-exists with the Attached Behaviors.
Many developers shy away from this immensely powerful feature. And we can use it along with RoutedCommands.
In your case
Attach to the Button, with a Action delegate.
Attach the string object as command parameter.
Inside the behavior, set the Button.Command with some Routed command.
In the executed event handler, get the button action delegate from the sender / originalsource / source as the button and then call your Action<> accordingly by using e.Parameter string value.
Sample code below...
Assume you have common button utilities of signature Action<Button, string>
public static class ButtonActionUtilities
{
public static Action<Button, string> ButtonActionDelegate
{
get
{
return ExecuteButtonClick;
}
}
public static void ExecuteButtonClick(Button btn, string param)
{
MessageBox.Show(
"You clicked button " + btn.Content + " with parameter " + param);
}
}
Then the attched behavior is as below...
public static class ButtonAttachedBehavior
{
public static readonly DependencyProperty ActionDelegateProperty
= DependencyProperty.RegisterAttached(
"ActionDelegate",
typeof(Action<Button, string>),
typeof(ButtonAttachedBehavior),
new PropertyMetadata(null, OnActionDelegatePropertyChanged));
public static Action<Button, string> GetActionDelegate(
DependencyObject depObj)
{
return (Action<Button, string>)depObj.GetValue(
ActionDelegateProperty);
}
public static void SetActionDelegate(
DependencyObject depObj, Action<Button, string> value)
{
depObj.SetValue(ActionDelegateProperty, value);
}
private static void OnActionDelegatePropertyChanged(
DependencyObject depObj,
DependencyPropertyChangedEventArgs e)
{
if (depObj is Button
&& e.NewValue is Action<Button, string>)
{
((Button)depObj).Command
= new RoutedCommand(
"ActionRoutedCommand",
typeof(ButtonAttachedBehavior));
((Button) depObj).CommandBindings.Add(
new CommandBinding(
((Button) depObj).Command,
OnActionRoutedCommandExecuted));
}
}
private static void OnActionRoutedCommandExecuted(
object sender, ExecutedRoutedEventArgs e)
{
var actionDelegate = GetActionDelegate((Button)e.Source);
actionDelegate((Button) e.Source, (string)e.Parameter);
}
}
And on XAML it will look like this....
<StackPanel>
<Button x:Name="TestButton" Content="Test Me"
local:ButtonAttachedBehavior.ActionDelegate
="{x:Static local:ButtonActionUtilities.ButtonActionDelegate}"
CommandParameter
="{Binding Text, ElementName=ParameterTextBox}"/>
<TextBox x:Name="ParameterTextBox"/>
</StackPanel>
So with the code above you will need to just set the ActionDelegate attached property to approapriate delegate and it will execute that.
I would still suggest you to revamp your existing code setup to separate button specific behaviors to make it more MVVM friendly.

Winforms, Combobox, Databinding... allow user to type in a value not found in the DataSource

Just like the title says... I have a Winforms application with a databound dropdown. I want the user to have the convenience to pick from a bunch of predefined values, but also the ability to type in his own value
If I just enable databinding and set dropdown type to anything but DropDownList, it allows me to enter anything I want, but does not persist it to the objects...
Seems like a simple problem to solve... help?
I've added an event handler on ComboBox.Leave this code would add the newly typed in string in the combobox to the underlying list(countries) as well as refresh the combobox binding to it.
Limitations
You'd have to handle the addition of new element based on the type of datasource you have.
The List.Contains is case sensitive you might want to keep all the strings in one case. And convert the user entered value to that case before deciding to add it to the datasource.
Here you go, modify the comboBox1_Leave eventhandler according to your datatypes and datasource.
public partial class Form1 : Form
{
private List<string> countries;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
countries = new List<string>();
countries.Add("Australia");
countries.Add("Belgium");
countries.Add("Canada");
comboBox1.DataSource = countries;
}
private void comboBox1_Leave(object sender, EventArgs e)
{
ComboBox combo = (sender as ComboBox);
CurrencyManager cm = (combo.BindingContext[combo.DataSource] as CurrencyManager);
if (!cm.List.Contains(combo.Text))
{
cm.List.Add(combo.Text);
cm.EndCurrentEdit();
cm.Refresh();
cm.Position = cm.Count - 1;
}
}
}

Silverlight dependency property snafu, what am I doing wrong?

I'm enhancing an open source control to add some functionality that I need, and I'm getting hopelessly tangled up in the following problem:
The control is a rich textbox that supports HTML but not via a property; you have to do something like this:
var sHtml = "..."
ctrl.LoadHtml(sHtml)
and
var sHtml = ctrl.SaveHtml()
So far so good. But I want to set the HTML via data binding, so I made a dependency property called Html:
public static readonly DependencyProperty HtmlProperty =
DependencyProperty.Register(
"Html",
typeof(string),
typeof(RichTextEditor),
new PropertyMetadata(string.Empty, new PropertyChangedCallback(HtmlChangedCallback))
);
public string Html
{
get {return (string)GetValue(HtmlProperty);}
set {SetValue(HtmlProperty, value);}
}
private static void HtmlChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//get the control
var rte = (RichTextEditor)d;
//got here, so load the html
rte.TextBox.LoadHtml((string)e.NewValue);
}
This all works fine. The problem I'm having is that I can't figure out how to notify the property system when the contents of the control have changed. The control has a ContentChanged event, so I tried this:
private void rtb_ContentChanged(object sender, RichTextBoxEventArgs e)
{
//tell the html prop that it changed
SetValue(HtmlProperty, rtb.SaveHtml());
}
But this then triggers the HtmlChangedCallback and the re-entrance caused problems. So then I tried using a re-entrance flag, but that got messy because the sequence of events is more complex than I would have expected, and around this point I figured I must be missing something, so I'm asking here. Please help! Thanks in advance.
BTW, the control doesn't support INotifyPropertyChanged, and implementing it is out of scope, because the control is big and I don't want to do that much work.
This is a classic little problem that is usually solved with a simple boolean flag.
private bool suppressHtmlChanged = false;
private static void HtmlChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var rte = (RichTextEditor)d;
if (!rte.suppressHtmlChanged)
rte.TextBox.LoadHtml((string)e.NewValue);
}
private void rtb_ContentChanged(object sender, RichTextBoxEventArgs e)
{
suppressHtmlChanged = true;
SetValue(HtmlProperty, rtb.SaveHtml());
suppressHtmlChanged = false;
}
These days such solutions seem so old fashioned don't they, often we can convince ourselves that such a "low-tech" solutions can't be right because they're not "elegant".
Why do you need to notify property system? Are you trying to update the source of the data binding? The control does not have to implement INotifyPropertyChanges; this interface has to be implemented on the class which provides the data for data binding.
What if you ignore Save/LoadHtml for now and just store HTML as a string? Does it work without problems?
Here is the example which works fine for me
class MyControl
{
public int Value
{
get
{
return (int)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
Count.Text = value.ToString();
}
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value",
typeof(int),
typeof(Vote),
new PropertyMetadata(0, OnValueChanged));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyControl)d).Value = (int)e.NewValue;
}
Count is a text block control.

Silverlight Required TextBox Attached Property

I have a need to create an attached property for a TextBox, that enforces a rule that content is required.
NOTE: Unfortunately I am not able to use data annotations, or SL4 validation frameworks.
The textboxes are displayed within the context of a View. The View is reused in many places. When tabbing / clicking between TextBoxes within the view I want a popup message to notify the user if they have left a 'Required' TextBox empty.
Now, I have this working via the LostFocus event:
public static readonly DependencyProperty RequiredProperty =
DependencyProperty.RegisterAttached("Required", typeof(bool), typeof(TextBoxRequiredService),
new PropertyMetadata(OnRequiredChanged));
public static bool GetRequired(DependencyObject d)
{
return (bool)d.GetValue(RequiredProperty);
}
public static void SetRequired(DependencyObject d, bool value)
{
d.SetValue(RequiredProperty, value);
}
private static void OnRequiredChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBox textBox = d as TextBox;
textBox.LostFocus += (s, args) => {
if (textBox.Text.Length == 0) {
MessageBox.Show("Required Field!");
textBox.Focus();
}
};
}
But this is getting triggered, obvioulsy, on every lost focus, and there are certain situations, for example closing the view, that i don't want the validation to execute.
So, does anyone have any good suggestions (or examples) on a way to get a Required Text Box service working within a definable scope of actions? Or perhaps some clever alternatives to LostFocus that I could use?
Thanks,
Mark

Resources