WPF - Bindable chat view with selectable text - c#

WPF - Bindable chat view with selectable text
I want to create a simple text chat app using WPF. And of course user should be able to select and, for example, copy text.
It's very easy to use, for example, ListView with ItemsSource bound to messages. And appearance can be tuned, but the main problem is text selection. It's possible to select text only in one single control (one message).
At the moment i use WebBrowser for showing messages. So i have tons of HTML+JS+CSS. I think i dont even have to say how terrible it is.
Can you please point me to right direction?

You could take a look at the FlowDocument for that. This class can be used for customizing the appearance of the blocks (paragraphs) similar to an ItemsControl, it can contain UI controls too (in case you need it). And of course, the text selection will work across the whole document.
Unfortunately, the FlowDocument doesn't support bindings, so you will have to write some code for that.
Let me give you an example. You could use a Behavior from the System.Windows.Interactivity namespace to create a reusable functionality extension for the FlowDocument class.
This is what you could start with:
<FlowDocumentScrollViewer>
<FlowDocument ColumnWidth="400">
<i:Interaction.Behaviors>
<myApp:ChatFlowDocumentBehavior Messages="{Binding Messages}">
<myApp:ChatFlowDocumentBehavior.ItemTemplate>
<DataTemplate>
<myApp:Fragment>
<Paragraph Background="Aqua" BorderBrush="BlueViolet" BorderThickness="1"/>
</myApp:Fragment>
</DataTemplate>
</myApp:ChatFlowDocumentBehavior.ItemTemplate>
</myApp:ChatFlowDocumentBehavior>
</i:Interaction.Behaviors>
</FlowDocument>
</FlowDocumentScrollViewer>
(the i namespace is xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity")
So there is our ChatFlowDocumentBehavior which has a bindable Messages property for displaying the chat messages. Also, there is an ItemTemplate property where you define how a single chat message should look like.
Note the Fragment class. This is just a simple wrapper (code below). The DataTemplate class won't accept a Paragraph as its content, but we need our items to be Paragraphs.
You can configure that Paragraph as you wish (like colors, fonts, maybe additional child items or controls etc.)
So, the Fragment class is a simple wrapper:
[ContentProperty("Content")]
sealed class Fragment : FrameworkElement
{
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
nameof(Content),
typeof(FrameworkContentElement),
typeof(Fragment));
public FrameworkContentElement Content
{
get => (FrameworkContentElement)GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
}
The behavior class has a little bit more code but isn't complicated.
sealed class ChatFlowDocumentBehavior : Behavior<FlowDocument>
{
// This is our dependency property for the messages
public static readonly DependencyProperty MessagesProperty =
DependencyProperty.Register(
nameof(Messages),
typeof(ObservableCollection<string>),
typeof(ChatFlowDocumentBehavior),
new PropertyMetadata(defaultValue: null, MessagesChanged));
public ObservableCollection<string> Messages
{
get => (ObservableCollection<string>)GetValue(MessagesProperty);
set => SetValue(MessagesProperty, value);
}
// This defines how our items will look like
public DataTemplate ItemTemplate { get; set; }
// This method will be called by the framework when the behavior attaches to flow document
protected override void OnAttached()
{
RefreshMessages();
}
private static void MessagesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ChatFlowDocumentBehavior b))
{
return;
}
if (e.OldValue is ObservableCollection<string> oldValue)
{
oldValue.CollectionChanged -= b.MessagesCollectionChanged;
}
if (e.NewValue is ObservableCollection<string> newValue)
{
newValue.CollectionChanged += b.MessagesCollectionChanged;
}
// When the binding engine updates the dependency property value,
// update the flow doocument
b.RefreshMessages();
}
private void MessagesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddNewItems(e.NewItems.OfType<string>());
break;
case NotifyCollectionChangedAction.Reset:
AssociatedObject.Blocks.Clear();
break;
}
}
private void RefreshMessages()
{
if (AssociatedObject == null)
{
return;
}
AssociatedObject.Blocks.Clear();
if (Messages == null)
{
return;
}
AddNewItems(Messages);
}
private void AddNewItems(IEnumerable<string> items)
{
foreach (var message in items)
{
// If the template was provided, create an instance from the template;
// otherwise, create a default non-styled paragraph instance
var newItem = (Paragraph)(ItemTemplate?.LoadContent() as Fragment)?.Content ?? new Paragraph();
// This inserts the message text directly into the paragraph as an inline item.
// You might want to change this logic.
newItem.Inlines.Add(message);
AssociatedObject.Blocks.Add(newItem);
}
}
}
Having this as a starting point, you can extend the behavior to suit your needs. E.g. add event handling logic for removing or reordering of the messages, implement comprehensive message templates etc.
It's almost always possible to implement the functionality with as less code as possible, using XAML features: styles, templates, resources etc. However, for the missing features, you just need to fall back to the code. But in that case, always try to avoid code-behind in the views. Create Behaviors or attached properties for that.

A textbox should give you what you are looking for I think. You will need to do the styling so it looks like you want but here is code:
XAML:
<TextBox Text="{Binding AllMessages}"/>
ViewModel:
public IEnumerable<string> Messages { get; set; }
public string AllMessages => GetAllMessages();
private string GetAllMessages()
{
var builder = new StringBuilder();
foreach (var message in Messages)
{
//Add in whatever for context
builder.AppendLine(message);
}
return builder.ToString();
}
You will probably want to use a RichTextBox for better formatting.

Related

How to create custom properties to a Template Control

I have a Template Control which has a StackPanel as it's root. What I want is a way to make something like this:
<Controls:MyControl Kind="SomeKind"/>
And based on SomeKind the StackPanel's background would change. There would be a limited number of "Kinds", the same way there is a limited number of HorizontalAlignments in a Button, for example.
<Button HorizontalAlignment="Center"/>
I've searched in the internet a bit and it does seems like it involves Attached Properties, but I haven't found a clean, simple and easy-to-follow example for UWP. I've found some examples for WPF but they doesn't seem to work.
No you don't need attached properties since it's likely to be only associated within your custom control. What you need is a dependency property of an enum type.
Say if you have this enum -
public enum PanelBackgroundType
{
Orange,
Pink,
Offwhite
}
Then your dependency property will look something like this -
public PanelBackgroundType PanelBackgroundType
{
get { return (PanelBackgroundType)GetValue(PanelBackgroundTypeProperty); }
set { SetValue(PanelBackgroundTypeProperty, value); }
}
// Using a DependencyProperty as the backing store for PanelBackgroundType. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PanelBackgroundTypeProperty =
DependencyProperty.Register("PanelBackgroundType", typeof(PanelBackgroundType), typeof(MyControl),
new PropertyMetadata(PanelBackgroundType.Offwhite, (s, e) =>
{
if ((PanelBackgroundType)e.NewValue != (PanelBackgroundType)e.OldValue)
{
// value really changed, invoke your changed logic here
var control = (MyControl)s;
switch ((PanelBackgroundType)(e.NewValue))
{
case PanelBackgroundType.Orange:
control.MyStackPanel.Background = new SolidColorBrush(Colors.Orange);
break;
case PanelBackgroundType.Pink:
control.MyStackPanel.Background = new SolidColorBrush(Colors.Pink);
break;
case PanelBackgroundType.Offwhite:
default:
control.MyStackPanel.Background = new SolidColorBrush(Colors.Wheat);
break;
}
}
else
{
// else this was invoked because of boxing, do nothing
}
}));
Note that I have a check (PanelBackgroundType)e.NewValue != (PanelBackgroundType)e.OldValue inside the property changed callback to see if the value of the dp really has changed. This may seem redundant but according to MSDN, this is the best practise as -
If the type of a DependencyProperty is an enumeration or a structure,
the callback may be invoked even if the internal values of the
structure or the enumeration value did not change. This is different
from a system primitive such as a string where it only is invoked if
the value changed. This is a side effect of box and unbox operations
on these values that is done internally. If you have a
PropertyChangedCallback method for a property where your value is an
enumeration or structure, you need to compare the OldValue and
NewValue by casting the values yourself and using the overloaded
comparison operators that are available to the now-cast values. Or, if
no such operator is available (which might be the case for a custom
structure), you may need to compare the individual values. You would
typically choose to do nothing if the result is that the values have
not changed.
Have a look at this link: https://msdn.microsoft.com/en-us/windows/uwp/xaml-platform/custom-dependency-properties
It will show you how to add custom dependency properties for an object that you'll be able to edit in the UI.
I'll give you a quick example of what you want event though I recommend you to take a look at Microsoft's docs
public sealed partial class MyControl : UserControl
{
public MyControl()
{
this.InitializeComponent();
}
public static readonly DependencyProperty KindProperty = DependencyProperty.Register(
"Kind", typeof(string), typeof(MyControl),
new PropertyMetadata(null, new PropertyChangedCallback(OnKindChanged)));
public string Kind
{
get { return (string)GetValue(KindProperty); }
set { SetValue(KindProperty, value); }
}
private static void OnKindChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Executed method when the KindProperty is changed
}
}

Master/detail view using TreeView

I'm working on implementing a master/details view in my application using a TreeView and a custom details view control. I'm also trying to stick to the MVVM pattern.
Right now the TreeView is bound to a collection of view model objects that contain all of the details and the details view is bound to the selected item of the TreeView.
This works great... until one of the TreeView nodes has 5,000 children and the application is suddenly taking up 500MB of RAM.
Main window view model:
public class MainWindowViewModel
{
private readonly List<ItemViewModel> rootItems;
public List<ItemViewModel> RootItems { get { return rootItems; } } // TreeView is bound to this property.
public MainWindowViewModel()
{
rootItems = GetRootItems();
}
// ...
}
Item view model:
public ItemViewModel
{
private readonly ModelItem item; // Has a TON of properties
private readonly List<ItemViewModel> children;
public List<ItemViewModel> Children { get { return children; } }
// ...
}
Here's how I'm binding the details view:
<View:ItemDetails DataContext="{Binding SelectedItem, ElementName=ItemTreeView}" />
I'm fairly new to WPF and the MVVM pattern, but it seems like a waste to I want to bind the TreeView to a collection of a smaller, simplified object that only has properties necessary for displaying the item (like Name and ID), then once it is selected have all of the details loaded. How would I go about doing something like this?
Overview
This should be a simple matter of binding the TreeView's selected item property to something on your source. However, because of the way the TreeView control was built, you have to write more code to get an MVVM-friendly solution, using out-of-the-box WPF.
If you're using vanilla WPF (which I'm assuming you are), then I'd recommend going with an attached behavior. The attached behavior would bind to an action on your main view model that would be invoked when the TreeView's selection changes. You could also invoke a command instead of an action, but I'm going to show you how to use an action.
Basically, the overall idea is to use one instance of your details view model that will be made available as a property of your master view model. Then, instead of your RootItems collection having hundreds of instances of view models, you can use light-weight objects that simply have a display name for the node and perhaps some kind of id field behind them. When the selection on your TreeView changes, you want to notify your details view model by either calling a method or setting a property. In the demonstration code below, I'm setting a property on the DetailsViewModel called Selection.
Walkthrough with Code
Here's the code for the attached behavior:
public static class TreeViewBehavior
{
public static readonly DependencyProperty SelectionChangedActionProperty =
DependencyProperty.RegisterAttached("SelectionChangedAction", typeof (Action<object>), typeof (TreeViewBehavior), new PropertyMetadata(default(Action), OnSelectionChangedActionChanged));
private static void OnSelectionChangedActionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var treeView = sender as TreeView;
if (treeView == null) return;
var action = GetSelectionChangedAction(treeView);
if (action != null)
{
// Remove the next line if you don't want to invoke immediately.
InvokeSelectionChangedAction(treeView);
treeView.SelectedItemChanged += TreeViewOnSelectedItemChanged;
}
else
{
treeView.SelectedItemChanged -= TreeViewOnSelectedItemChanged;
}
}
private static void TreeViewOnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var treeView = sender as TreeView;
if (treeView == null) return;
InvokeSelectionChangedAction(treeView);
}
private static void InvokeSelectionChangedAction(TreeView treeView)
{
var action = GetSelectionChangedAction(treeView);
if (action == null) return;
var selectedItem = treeView.GetValue(TreeView.SelectedItemProperty);
action(selectedItem);
}
public static void SetSelectionChangedAction(TreeView treeView, Action<object> value)
{
treeView.SetValue(SelectionChangedActionProperty, value);
}
public static Action<object> GetSelectionChangedAction(TreeView treeView)
{
return (Action<object>) treeView.GetValue(SelectionChangedActionProperty);
}
}
Then, in the XAML on your TreeView element, apply the following: local:TreeViewBehavior.SelectionChangedAction="{Binding Path=SelectionChangedAction}". Note that you will have to substitute local for the namespace of the TreeViewBehavior class.
Now, add the following properties to your MainWindowViewModel:
public Action<object> SelectionChangedAction { get; private set; }
public DetailsViewModel DetailsViewModel { get; private set; }
In your MainWindowViewModel's constructor, you need to set the SelectionChangedAction property to something. You might do SelectionChangedAction = item => DetailsViewModel.Selection = item; if your DetailsViewModel has a Selection property on it. That's entirely up to you.
And finally, in your XAML, wire the details view up to its view model like so:
<View:ItemDetails DataContext="{Binding Path=DetailsViewModel}" />
That's the basic architecture of an MVVM friendly solution using straight WPF. Now, with that said, if you're using a framework like Caliburn.Micro or PRISM, your approach would probably be different than what I've provided here. Just keep that in mind.

Listview MouseDoubleClickEvent create in behind code

When I have:
<UserControl.Resources>
<Style x:Key="itemstyle" TargetType="{x:Type ListViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="HandleDoubleClick" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</UserControl.Resources>
<Grid>
<ListView ItemContainerStyle="itemstyle" Name="listView1" >
<ListView.View>
<GridView>
... etc
Code behind
protected void HandleDoubleClick(object sender, MouseButtonEventArgs e)
{
T item = (T)(((ListViewItem)sender).Content);
// item is the row item that was double clicked on
}
Everything works great.
Now I need to do the same thing on code behind. This is what I have worked out:
public Constructor(){
listview.AddHandler(
ListViewItem.MouseDoubleClickEvent,
new MouseButtonEventHandler(HandleDoubleClick)
);
}
protected void HandleDoubleClick(object sender, MouseButtonEventArgs e)
{
T item = (T)(((ListViewItem)sender).Content);
// error cause the sender is the listview
}
that event fires when I double click any part of the listview not just the listviewItem. Also I expect sender to be ListViewItem and it is not. the sender is actually the listview. Things that I have tried:
1) Because the sender is the listview I tried creating the event as:
listview.AddHandler(
// I use PreviewMouseDoubleClickEvent instead of MouseDoubleClickEvent because of the way the events bubles
ListViewItem.PreviewMouseDoubleClickEvent,
new MouseButtonEventHandler(HandleDoubleClick)
);
I get the same error the sender is the listview
2) Instead of doing:
T item = (T)((ListViewItem)sender).Content;
I do:
T item = (T)(listview.selectedItem);
the problem with this is that if the user double clicks anything on the listview that is not the row it will return the current selected item
why is my code not working? what am I doing wrong?
Attached Behavior Pattern
I think you should use attached behavior because
MVVM ♥ Attached Behavior
It is reusable: once you create the attached behavior and the commands for a double click, you can attach it to any other control type (so you could use the same code to double click an image etc)
Abstracts the dirty implementation so the xaml and the view model are both much more legible
The way they work is awesome, and I love using clever code.
Heres what you do:
Create the ListViewItemDoubleClickCommandBehavior
As you can see from the above diagram, this CommandBehavior class links the command to the control.
In the case above its for a buttonbase, but we are gonna make a ListViewItem one
You are going to need prism for this one, as the CommandBehaviorBase, which this inherits from, is part of that library. You can get it all here (but for this example you only need to Prism.Commands dll)
Naming convention says you should name this CommandBehavior in the format
[X][Y]CommandBehavior
X - The name of the control to which we are binding the command (in this case ListViewItem)
Y - The Action the user performs that will cause the command to happen (in this case DoubleClick)
Heres the code:
public class ListViewItemDoubleClickCommandBehaviour : CommandBehaviorBase<ListViewItem>
{
public ListViewItemDoubleClickCommandBehaviour(ListViewItem controlToWhomWeBind)
: base(controlToWhomWeBind)
{
controlToWhomWeBind.MouseDoubleClick += OnDoubleClick;
}
private void OnDoubleClick(object sender, System.Windows.RoutedEventArgs e)
{
ExecuteCommand();
}
}
Create a DoubleClick Static class
This class will house all the double click command stuff.
Its called doubleclick because it represents the actual act of double clicking. Each other kind of action (say you wanted a command for button down in a textbox) will have its own class with its command, commandparameter and behavior can we can then access.
The commands are dependency properties of type ICommand (I'm assuming you have implemented this interface, because you kinda need it for MVVM)
One dependency property for the command itself and one for the parameters the command needs (in this case you will probably use the selected item as a parameter)
This class has an instance of the ListViewItemDoubleClickCommandBehavior as a dependency property. This is how the link is created between the command you bind to your control and the double click event in the ListViewItemDoubleClickCommandBehaviour. We use some of the CommandBehaviorBase's magic methods to create the behavior and pass it the command to execute.
The OnSetCommand and OnSetCommandParameter callbacks are used to wire the Behavior to the command. Every time the command changes, we set that as the new command for the behavior to run. These callbacks are registered to the DependencyProperties in the PropertyMetadata part of the constructor. These get fired whenever the dependency property is changed.
Here is that class's code:
public static class DoubleClick
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(DoubleClick),
new PropertyMetadata(OnSetCommandCallback));
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached(
"CommandParameter",
typeof(object),
typeof(DoubleClick),
new PropertyMetadata(OnSetCommandParameterCallback));
private static readonly DependencyProperty DoubleClickCommandBehaviorProperty =
DependencyProperty.RegisterAttached(
"DoubleClickCommandBehavior",
typeof(ListViewItemDoubleClickCommandBehaviour),
typeof(DoubleClick),
null);
public static void SetCommand(ListViewItem controlToWhomWeBind, ICommand value)
{
controlToWhomWeBind.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(ListViewItem controlToWhomWeBind)
{
return (ICommand)controlToWhomWeBind.GetValue(CommandProperty);
}
public static void SetCommandParameter(ListViewItem controlToWhomWeBind, ICommand value)
{
controlToWhomWeBind.SetValue(CommandParameterProperty, value);
}
public static ICommand GetCommandParameter(ListViewItem controlToWhomWeBind)
{
return (ICommand)controlToWhomWeBind.GetValue(CommandParameterProperty);
}
private static void OnSetCommandCallback(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
ListViewItem controlToWhomWeBind= dependencyObject as ListViewItem;
if (controlToWhomWeBind!= null)
{
ListViewItemDoubleClickCommandBehaviour behavior = GetOrCreateBehavior(controlToWhomWeBind);
behavior.Command = e.NewValue as ICommand;
}
}
private static void OnSetCommandParameterCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
ListViewItem controlToWhomWeBind= dependencyObject as ListViewItem;
if (controlToWhomWeBind!= null)
{
ListViewItemDoubleClickCommandBehaviour behavior = GetOrCreateBehavior(controlToWhomWeBind);
behavior.CommandParameter = e.NewValue;
}
}
private static ListViewItemDoubleClickCommandBehaviour GetOrCreateBehavior(
ListViewItem controlToWhomWeBind)
{
ListViewItemDoubleClickCommandBehaviour behavior =
controlToWhomWeBind.GetValue(DoubleClickCommandBehaviorProperty) as
ListViewItemDoubleClickCommandBehaviour;
if (behavior == null)
{
behavior = new ListViewItemDoubleClickCommandBehaviour (controlToWhomWeBind);
controlToWhomWeBind.SetValue(DoubleClickCommandBehaviorProperty, behavior);
}
return behavior;
}
}
Note:
I know this looks like alot of complex code, but its a simple pattern once you use it often enough, and it abstracts all the complexity out of your view model, so that the binding of the command to the control looks remarkably simple.
Create the command in your viewmodel and bind to it in the xaml
Now that the grunt work is over, we do the easy part.
Creating the Command
I assume that you are familiar with commands, you create the command as you always do:
public ICommand DisplaySelectedItemCmd { get; protected set; }
//This method goes in your constructor
private void InitializeCommands()
{
//Initializes the command
this.DisplaySelectedItemCmd = new RelayCommand(
(param) =>
{
this.DisplaySelectedItem((object)param);
},
(param) => { return this.CanDisplaySelectedItem; }
);
}
//The parameter should be your listview's selected item. I have no idea what type it is so I made it an object
public void DisplaySelectedPolicy(object selectedListViewItem)
{
//Code to perform when item is double clicked
}
private bool CanDisplaySelectedPolicy
{
get
{
return true; //Change this bool if you have any reason to disable the double clicking, as this bool basically is linked to the double click command firing.
}
}
Creating the binding in xaml
And now for the beautiful part.
First add the xml namespace:
xmlns:commands="clr-namespace:CommandHandling"
and then bind on your ListViewItem
<Style TargetType="{x:Type ListViewItem}" x:Key="{x:Type ListViewItem}" >
<Setter Property="commands:DoubleClick.Command" Value="{Binding Path=bleh}"/>
</Style>
and done.
If this doesn't work let me know. (And anyone reading this in the future, you can ask me for help if you like)
u_u
In xaml you attach the event handler to the ListViewItem while in code behind you attach it to the ListView itself. That is why you get different behaviour. If you want to do the same in code behind you will have to loop al items in your items collection and bind the DoubleClick event of each one to your handler.
If there is no real reason to do this in code behind I would go for the xaml approach which fits the MVVM pattern better where you try to keep as few code as possible in the code behind.
Don't see why you are doing this in code behind unless you are wanting named content in your UserControl so you are changing it to a custom control. Try repeating your xaml code in code behind, ie create a style for ListViewItem and setting it as ItemContainerStyle for your listview.
Figure it out!! I am sure it should be the same with the double click...
In xaml I have:
<ListView IsSynchronizedWithCurrentItem="True" Name="listView" Margin="32,158,66,0" VerticalAlignment="Top">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="PreviewMouseUp" Handler="itemClicked"></EventSetter>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
... etc
and I can create the same thing with c# on code behind as:
EventSetter ev = new EventSetter();
ev.Event = ListViewItem.PreviewMouseUpEvent;
ev.Handler = new MouseButtonEventHandler(itemClicked);
Style myStyle = new Style();
myStyle.TargetType = typeof(ListViewItem);
myStyle.Setters.Add(ev);
listView.ItemContainerStyle = myStyle;
....
void itemClicked(object sender, MouseButtonEventArgs e)
{
// item was licked in listview implement behavior in here
}

Control an Adorner's visibility using a Property?

Would it be possible for an Adorner to be hidden / displayed depending on the value of a property in a class ?
Should I use attached properties for this purpose ?
If so, how exactly can the Adorner's visibility be controlled; do I have to manually remove it / add it to the Adorner Layer within the Dependency Object's OnChanged event ?
This is just a very quick code representation of what I'm trying to do:
(Note: I'm not even sure if its the right way of doing things. I want the Adorner's visibility to be controlled by the value of a property that is modified by the code in my business model. The problem with Attached Properties is that its the control's responsibility to update the value of the property instead of the code in my business domain.)
public static class IsValidBehavior
{
public static readonly DependencyProperty IsValidProperty = DependencyProperty.RegisterAttached("IsValid",
typeof(bool),
typeof(IsValidBehavior),
new UIPropertyMetadata(false, OnIsValidChanged));
public static bool GetIsValid(DependencyObject obj)
{
return (bool)obj.GetValue(IsValidProperty);
}
public static void SetIsValid(DependencyObject obj, bool value)
{
obj.SetValue(IsValidProperty, value);
}
private static void OnIsValidChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
UIElement element = dependencyObject as UIElement;
if (element == null)
return;
if ((bool)e.NewValue == true)
{
// Display the Adorner
}
else
{
// Hide the Adorner
}
}
}
Well, if I right understood your question, in WPF you have 2 ways to do that, from code or from XAML. From code, you more or less already did, in XAML you can do something like this, I think:
Visibility="{Binding Path=MyVisibilityVariant,
Converter={StaticResource VisibilityConverter}}
In other words bind it to some property. My general suggestion: is use XAML whenever you can, considering a couple of variants:
XAML declaration makes the software very scallable, but also more complex (consider your, or your group cappabilities, somehow doing the stuff in code behind is best, if not only solution available)
Consider you deadlines, cause on XAML stuff implementing/debugging/fixing you will spend more time then on code.
EDIT
Defining custom Adorder in order to be able to define it in XAML

Using a custom attached property with a binding

I was looking at this question, but I don't understand how to actually USE the created AttachedProperty. The problem is trying to have a binding on the source of the WebBrowser control.
The code there looks like:
public static class WebBrowserUtility
{
public static readonly DependencyProperty BindableSourceProperty =
DependencyProperty.RegisterAttached("BindableSource", typeof(string), typeof(WebBrowserUtility), new UIPropertyMetadata(null, BindableSourcePropertyChanged));
public static string GetBindableSource(DependencyObject obj)
{
return (string) obj.GetValue(BindableSourceProperty);
}
public static void SetBindableSource(DependencyObject obj, string value)
{
obj.SetValue(BindableSourceProperty, value);
}
public static void BindableSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
WebBrowser browser = o as WebBrowser;
if (browser != null)
{
string uri = e.NewValue as string;
browser.Source = uri != null ? new Uri(uri) : null;
}
}
}
and
<WebBrowser ns:WebBrowserUtility.BindableSource="{Binding WebAddress}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
Width="300"
Height="200" />
The WebAddress, what is that exactly? This is my understanding (which is probably wrong):
There's an AttachedProperty that can be attached to any object, and in this particular case, it is basically just attaching a property called BindableSource which is of type String.
When we have the "{Binding WebAddress}" it means that in some c# code somewhere that handles this .xaml file there's something that looks like:
public String WebAddress
{
// get and set here? not sure
}
And to take advantage of the property changed, I can called RaisedPropertyChanged and it will fire that static method up there?
Even when I look at it, it doesn't seem right, but I can't find anything online to help me.
There's an AttachedProperty that can be attached to any object, and in this particular case, it is basically just attaching a property called BindableSource which is of type String.
You might want to read the MSDN article on attached properties.
It is rather simple: Dependency properties work with dictionaries in which controls are associated with their values for a property, this makes it quite easy to add something like attached properties which can extend a control.
In the RegisterAttached method of the attached property a PropertyChangedCallback is hooked up which will be executed if the value changes. Using a dependency property enables binding which is the point of doing this in the first place. All the property really does is call the relevant code to navigate the browser if the value changes.
When we have the "{Binding WebAddress}" it means that in some c# code somewhere that handles this .xaml file there's something that looks like [...]
The binding references some public property or depedency property (not a field) called WebAddress inside the DataContext of the WebBrowser. For general information on data-binding see the Data Binding Overview.
So if you want to create a property which should be a binding source you either implement INotifyPropertyChanged or you create a DependencyProperty (they fire change notifications on their own and you normally do only create those on controls and UI-related classes)
Your property could look like this:
public class MyModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private string _webAddress;
public string WebAddress
{
get { return _webAddress; }
set
{
if (value != _webAddress)
{
_webAddress = value;
NotifyPropertyChanged("WebAddress");
}
}
}
}
Here you have to raise the PropertyChanged event in the setter as you suspected. How to actually declare working bindings in XAML is a rather broad topic sp i would like to direct you to the aforementioned Data Binding Overview again which should explain that.
And to take advantage of the property changed, I can called RaisedPropertyChanged and it will fire that static method up there?
The event is fired to trigger the binding to update, this in turn changes the value of the attached property which in turn causes the PropertyChangedCallback to be executed which eventually navigates the browser.

Categories