I need to write a custom control that looks like a TextBox and that contains a method called Refresh() which main purpose will be to clear the Text and to roll back few other values.
The method shall become bindable somehow so that others can bind a property from their ViewModel with it. Hence why I am thinking that inside my custom control I will need an dependency property of type Action.
So far so logical but next problem is the method/dp may no get overriden on control side once users sets a two way binding on it. Basically I always have to deliver the method wrapper as Action to ViewModel and inside ViewModel other users may call it.
How do I do all this? It seems to me that I have to somehow get the binding of the method work like OneWayToSource.
I apologize in case its a duplicate. Futhermore thanks in advance guys.
EDIT: Please no alternative solutions. Those are the requirements and I have to stick to them.
I think that the simplest thing you can do here is to expose a bool property, maybe called IsCleared, and just call your method from that property when it becomes true. Exposing ICommand and/or delegate objects transfers the functionality out of your control, so you can't use those.
#ninjahedgehog, why can't you use a bool 'switch' property? Your requirement says 'so that others can bind a property from their ViewModel with it'... they can bind to a bool property from their view model. In my opinion, it seems to be your only option. As I said earlier, you can't use ICommand and/or delegate objects as that would transfer the functionality out of your control - that would enable other developers to write their own functionality rather than to just call yours.
What you really want is a method on your control that they could call from their view model... but view models shouldn't have any knowledge about the view controls, so you can't do that. The next best thing to that is creating a method that is called when a property is given a certain value. Here you have a few choices.
If you really don't like the bool switch idea, then how about an enum property? Create an enum with specific values like ClearText and whatever other functionality you would like to expose. Then the other developers simply set this property to the relevant instance to instantiate that functionality... I only suggested the bool switch property because it seems as if you only want to expose one piece of functionality.
One last point to note about using the bool switch property... as it is a switch, you need to reset it after use, or just never actually set it:
public bool IsTextClear
{
get { if (value) ClearText(); }
}
I dont know why you need this coz the person who is using your control can directly call the method from the code behind. But if you want that there should be some property like ClearMe on control and when set to true it should clear the control then you can define the dependency property and listen to its change in control like below and call Refresh from there.
public static readonly DependencyProperty ClearMeProperty = DependencyProperty.Register
(
"ClearMe",
typeof(bool),
typeof(MyControl),
new FrameworkPropertyMetadata(false, OnClearMeChanged)
);
public bool ClearMe
{
get { return (bool)GetValue(ClearMeProperty); }
set { SetValue(ClearMeProperty, value); }
}
private static void OnClearMeChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as MyControl;
if((bool)e.NewValue)
{
control.Refresh()
}
}
and you can bind this property to your ViewModel property. whenever ViewModel property will change to true. Property Change will be fired in control and will refersh it.
I editted my answer, as I wasn't understanding what you wanted. The only way I could come up with to do what you want was to use an Action DependencyProperty on the CustomControl and bind that to the ViewModel using a OneWayToSource binding, that way the Action from the control gets sent to the viewmodel. Within your customcontrol, you can test to make sure that only OneWayToSource binding is used and do something if not.. in this case, I add some text and made the background red.
View
<UserControl x:Class="WpfApplication1.Views.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
xmlns:controls="clr-namespace:WpfApplication1.Controls">
<UserControl.Resources>
<vm:TestViewModel x:Key="TestViewModel" />
</UserControl.Resources>
<StackPanel DataContext="{StaticResource TestViewModel}">
<StackPanel Orientation="Horizontal" Height="30">
<controls:CustomTextBox Width="300" Refresh="{Binding RefreshAction, Mode=OneWayToSource}" />
<Button Content="Refresh" Width="80" Command="{Binding RefreshFromView}" />
</StackPanel>
</StackPanel>
ViewModel
using System;
using System.ComponentModel;
namespace WpfApplication1.ViewModels
{
public class TestViewModel : INotifyPropertyChanged
{
public TestViewModel()
{
RefreshFromView = new RelayCommand(ExecuteRefreshFromView);
}
public Action RefreshAction { get; set; }
public RelayCommand RefreshFromView { get; set; }
private void ExecuteRefreshFromView(object parameter)
{
if (RefreshAction != null)
RefreshAction();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyOfPropertyChange(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Custom Control
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace WpfApplication1.Controls
{
public class CustomTextBox : TextBox
{
public CustomTextBox()
{
this.Loaded += CustomTextBox_Loaded;
}
void CustomTextBox_Loaded(object sender, RoutedEventArgs e)
{
BindingExpression bindingExpression = GetBindingExpression(RefreshProperty);
BindingMode mode = bindingExpression.ParentBinding.Mode;
if (mode != BindingMode.OneWayToSource)
{
Text = "Use OneWayToSource Binding only!";
Background = new SolidColorBrush(Colors.Red);
}
Refresh = new Action(DoRefresh);
}
private void DoRefresh()
{
Text = null;
}
public Action Refresh
{
get { return (Action)GetValue(RefreshProperty); }
set { SetValue(RefreshProperty, value); }
}
public static readonly DependencyProperty RefreshProperty = DependencyProperty.Register("Refresh", typeof(Action), typeof(CustomTextBox));
}
}
You could use a Command:
public class Command : ICommand
{
public void Execute(object parameter)
{
// Do whatever you have to do
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
In your ViewModel:
public ICommand Command { get; set; }
In your XAML (assuming that your Custom Control is composed of a TextBox and a Button for example):
<Button Click="{Binding Command}" />
Related
I have a list of objects (ObservableCollection subjectlist) and want to display them in a Combobox via data-binding and dependency property.
WPF Data Binding to a Combo Box
I searched on stackoverflow and tried to implement the solution of Craig Suchanec in the link above. (tried the whole day now and I just don't get what's wrong with my code)
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public static readonly DependencyProperty SubjectListProperty =
DependencyProperty.Register("SubjectList",
typeof(ObservableCollection<Subject>),
typeof(MainWindow));
private ObservableCollection<Subject> subjectList = new ObservableCollection<Subject>();
Initialization init1;
public ObservableCollection<Subject> SubjectList
{
get { return (ObservableCollection<Subject>)GetValue(SubjectListProperty); }
// get { return subjectList; }
}
public MainWindow()
{
init1 = new Initialization();
subjectList = init1.createMenuSubject();
InitializeComponent();
//this.comboBox.DataContext = SubjectList;
}
}
MainWindow.xaml
<Grid>
<ComboBox x:Name="comboBox" HorizontalAlignment="Left"VerticalAlignment="Top" Width="120" Margin="321,10,0,0"
ItemsSource="{Binding ElementName=mainWindow, Path=SubjectList}" DisplayMemberPath="Name"/>
</Grid>
It DOES work if I just set the DataContext and work without dependency property, but as soon as I try to use the dependency property for data-binding it does NOT and I don't see the significant difference between my implementation and the solution given in the link.
It would be much appreciated, if somebody could help me with this problem.
I can't see anywhere in your code where you are actually setting the value of the SubjectList property.
You are however setting the value of subjectList, but you're binding to SubjectList. Note the casing difference.
You should write:
public ObservableCollection<Subject> SubjectList
{
set { base.SetValue(SubjectListProperty, value); }
get { return (ObservableCollection<Subject>)base.GetValue(SubjectListProperty); }
}
instead of
public ObservableCollection<Subject> SubjectList
{
set { base.SetValue(SubjectListProperty, value); }
get { return subjectList; }
}
or any other ad hoc format. You are setting subjectList in your constructor MainWindow(), however, it will not set the value of SubjectList (with Capital S) and a property change event is never raised. Remove subjectList.
If you are wondering why the DataContext approach works, you should note it will work even if you do not use a DepenedencyProperty. However, if you implement INotifyPropertyChange, it will work with setting ElementName too.
There are two Viewmodels, both of them had implemented the INotifyPropertyChanged interface (I have called the OnpropertyChanged("propertyname") in my actual code).
Public Class A{
public B BProperty
{
get
{
return _BProperty;
}
set
{
if (_BProperty != null)
_BProperty.PropertyChanged -= _BProperty_PropertyChanged;
_BProperty = value;
OnPropertyChanged("BProperty");
if (_BProperty != null)
_BProperty.PropertyChanged += _BProperty_PropertyChanged;
}
}
void _BProperty_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "status")
{
OnPropertyChanged("BProperty");
}
}
B _BProperty;
}
Public Class B
{
public int status{get;set;}
}
I also had a userControl:
<MyUserControl ...
... >
<Grid>
</Grid>
</MyUserControl>
And I had a dependencyProperty:
/// <summary>
/// File status
/// </summary>
public int Filestatus
{
get { return (int)GetValue(FilestatusProperty); }
set { SetValue(FilestatusProperty, value); }
}
public static readonly DependencyProperty FilestatusProperty =
DependencyProperty.Register(
"Filestatus",
typeof(int),
typeof(MyUserControl),
new PropertyMetadata(0, OnFilestatusPropertyChanged));
private static void OnFilestatusPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyUserControl control = (MyUserControl)d;
if (e.NewValue != null)
{
}
}
edit:2015/09/21
Add the get/set methods:
public static readonly DependencyProperty FileStatusProperty = DependencyProperty.RegisterAttached(
"FileStatus", typeof(int), typeof(FileStatusIconControl), new PropertyMetadata(0, PropertyChangedCallback));
public static int GetFileStatus(DependencyObject source)
{
return (int)source.GetValue(FileStatusProperty);
}
public static void SetFileStatus(DependencyObject target, int value)
{
target.SetValue(FileStatusProperty, value);
}
private static void PropertyChangedCallback(
DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
FileStatusIconControl fsic = dependencyObject as FileStatusIconControl;
if(fsic != null)
wahahahahaha;
}
edit end
I used this userControl in my mainPage like this:
<mainPage ...
...>
<Grid>
<MyUserControl Filestatus={Binding Bproperty.status} />
<TextBox Text={Binding Bproperty.status} />
</Grid>
</mainPage>
The datacontext of mainPage is an instance of Class A.
My question is:
When status is changed, the text of textbox is changed, but the OnFilestatusPropertyChanged method only was called once when Usercontrol is loaded. Why?
Thanks.
I will start by saying that while I was looking into your question I ran into some problems with the code you have provided. I appreciate that somewhere you have some real code which has a problem and you cannot share this code with us so have tried to reproduce the problem in a smaller set of files.
However, if you do do this, please at least verify that the code you have provided runs and exhibits the problem. It is evident that you haven't tried to run your sample code (particularly the XAML) as there are problems with it:
attribute values should be surrounded in double-quotes,
the binding path Bproperty.status should be BProperty.status (first P capitalised).
All these things slow down someone trying to help you. Worse still, when I do find a problem with your code I can't be sure whether it's a genuine problem that your real code also has or whether it's something you introduced putting together your sample code. So all I can do is point out all the problems I find in the hope that one of them is the problem you have in your real code.
Firstly, your TextBox's Text property binding doesn't contain Mode=TwoWay. In WPF, this binding is TwoWay by default, but in Silverlight all bindings are OneWay by default. If you are familiar with WPF, this may be why you omitted Mode=TwoWay.
Secondly, I don't see why you have implemented class B as you have, apparently leaving it up to class A to fire property-changed events on its behalf. This approach doesn't work: when Silverlight updates the value in the status property of a B instance, it does so by calling the status setter of the B instance. Your B class has only an autogenerated property setter, which certainly doesn't fire the PropertyChanged event. Because this event wasn't fired, Silverlight doesn't know that is has some updates to do, and furthermore your A class isn't aware that it has changed either.
I would implement INotifyPropertyChanged in the usual way in class B, by calling OnPropertyChanged in the status setter. I would also remove the BProperty_PropertyChanged event handler in class A as I don't think it does you any favours.
I am creating a control in WPF which have inside a label and I have created a dependency property in order to modify the label's visibility property.
My problem is that I cant find a way to change my label's visibility property at the same time my dependency property is assigned.
My code is as below:
public static readonly DependencyProperty captionVisibleProperty = DependencyProperty.Register(
"CaptionVisible",
typeof(bool),
typeof(MyCustomControl));
public bool CaptionVisible
{
get
{
return (bool)GetValue(captionVisibleProperty);
}
set
{
SetValue(captionVisibleProperty, value);
ShowCaption();
}
}
private void ShowCaption()
{
if (CaptionVisible)
{
captionLabel.Visibility = System.Windows.Visibility.Visible;
}
else
{
captionLabel.Visibility = System.Windows.Visibility.Collapsed;
}
}
As you can see I have tried to call my ShowCaption() method when my property is set, but nothing happens.
So, what I am supposed to do in order to get it done?
Hope someone can help me. Thank you in advance.
ShowCaption() shouldn't be necessary. Instead, just bind the label's Visibility property to your CaptionVisible property in xaml. It's also best to follow the Model-View-ViewModel design pattern for keeping your code organized. This means putting the logic for controlling your user interface (the View) in separate ViewModel classes, and then assigning ViewModel to that View's DataContext property.
That will make the binding a lot easier. Referencing properties that belong to user interface elements can sometimes be a bit of a hassle in WPF. By contract, WPF's bindings system was specifically designed to make it easy to get at the contents of a user interface element's DataContext.
You'll also need to use the handy-dandy BooleanToVisiblityConverter to make the binding work, since the Visibility property's type isn't bool. I like to put it in in the window's (or control's) resource dictionary for easy access:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<Label Visibility="{Binding Path=CaptionVisible,
Converter={StaticResource BooleanToVisibilityConverter}}">
<!-- label content -->
</Label>
As a side note, unless CaptionVisible is going to be the target of a binding, making it a dependency property is overkill. In this binding it's only the source, so just implementing INotifyPropertyChanged would be sufficient:
class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private bool _captionVisible;
public bool CaptionVisible
{
get { return _captionVisible; }
set
{
if(_captionVisible != value)
{
_captionVisible = value;
RaisePropertyChanged("CaptionVisible");
}
}
}
}
I have found a solution to this problem:
If you need to link a nested control dependency property to a container control dependency property you can do this:
public static readonly DependencyProperty captionLabelVisibilityProperty = DependencyProperty.Register(
"CaptionVisibility",
typeof(Visibility),
typeof(MyContainerControl),
new FrameworkPropertyMetadata(
VisibilityPropertyChangedCallback));
public Visibility CaptionVisibility
{
get
{ return (Visibility)GetValue(captionLabelVisibilityProperty); }
set
{ SetValue(captionLabelVisibilityProperty, value); }
}
private static void VisibilityPropertyChangedCallback(DependencyObject controlInstance, DependencyPropertyChangedEventArgs args)
{
MyContainerControl myContainerControlInstance = (MyContainerControl)controlInstance;
myContainerControlInstance.myLabel.Visibility = (Visibility)args.NewValue;
}
The setup
So I have a class, ComputerItem, designed to store everything I need to know about a specific computer; these items are stored in an ObservableCollection<ComputerItem>. I then have a custom control ComputerControl, which has (among other things) a few text boxes bound to members of ComputerItem, the bindings made available like so:
<TextBlock Name="tb_computerName"TextWrapping="Wrap" Text="{Binding ElementName=ComputerControl1, Path=computerName}"/>
and in the code behind
public static DependencyProperty computerNameProperty = DependencyProperty.Register("computerName", typeof(string), typeof(ComputerControl), null);
I then create a MultiselectList of ComputerControl objects:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<toolkit:MultiselectList x:Name="lb_computers" IsSelectionEnabledChanged="lb_computers_IsSelectionEnabledChanged"> <!--SelectionChanged="lb_computers_SelectionChanged" >-->
<toolkit:MultiselectList.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="sp">
<local:ComputerControl computerName="{Binding Name}" MACAddress="{Binding DisplayMAC}" playClicked="playClicked_Handler" editClicked="editClicked_Handler"/>
</StackPanel>
</DataTemplate>
</toolkit:MultiselectList.ItemTemplate>
</toolkit:MultiselectList>
</Grid>
and you can see the data bindings in the ComputerControl definition. In the code behind I bind the ObservableCollection to the MultiselectList:
this.lb_computers.ItemsSource = ComputerListMaintainer.GetList();
and all of this (as well as a few things I'm sure I've forgotten to include here) works beautifully to fill the MultiselectList with ComputerControls representing the ComputerItems in the ObservableCollection.
The problem
My issue is that when the underlying ComputerItem changes, the TextBlocks in the corresponding ComputerControl don't update. I've implemented INotifyPropertyChanged in the ComputerItem class:
public class ComputerItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string name;
protected virtual void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public string Name
{
get { return name; }
set { OnPropertyChanged("Name"); name = value; }
}
}
but that didn't solve the problem. I suspect it's something to do with ComputerControl but I have no idea where to start looking; the closest question I've found suggested INotifyPropertyChanged should have been the solution but they weren't using a custom control in that case, just a custom class, if I remember correctly.
What am I missing?
Well your setter is incorrect for starters; also do look into MvvmLight, as it provides a great API for this kind of plumbing work.
public string Name
{
get { return name; }
set
{
if(value != name)
{
name = value;
OnPropertyChanged("Name");
}
}
}
Update:
You shouldn't be setting lb_computers.ItemsSource in your code behind, because that's one time operation and not a binding. It is better to bind to an ObservableCollection of observable objects (aka INotifyPropertyChanged).
Also I'm not sure if you're properly declaring your dependency property, so below you can find a proper setup on how to define a 'bindable' property.
And also with XAML, the architecture of your code matters, to have a sane experience. I highly recommend that you utilize the Mvvm pattern. I find MvvmLight and MEFedMVVM to be great aids in my development. This require a bit of work at the beginning, but it'll be far easier to debug future issues and maintain your code.
If these tips don't help, then I'd have to see your full code.
Declaring a Bindable Property
#region ReportName
public string ReportName
{
get { return (string)GetValue(ReportNameProperty); }
set { SetValue(ReportNameProperty, value); }
}
public static readonly DependencyProperty ReportNameProperty = DependencyProperty.Register("ReportName",
typeof(string), typeof(ExportableGridView), new PropertyMetadata("Report", new PropertyChangedCallback(OnReportNameChanged)));
public static void OnReportNameChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ExportableGridView control = sender as ExportableGridView;
control.titleTextBlock.Text = e.NewValue as string;
}
#endregion ReportName
I'm fairly new to WPF and I have some problems getting databinding to work as I want. I've written a user control which contains a TextBox whose Text-Property I want to bind to a property of my UserControl, which I want to bind again to something else.
What am I missing?
XAML
<!-- User Control -->
<TextBox Text="{Binding Path=TheText}" />
<!-- Window -->
<WpfApplication1:SomeControl TheText="{Binding Path=MyStringProp}" />
C#
// User Control ----
public partial class SomeControl : UserControl
{
public DependencyProperty TheTextProperty = DependencyProperty
.Register("TheText", typeof (string), typeof (SomeControl));
public string TheText
{
get
{
return (string)GetValue(TheTextProperty);
}
set
{
SetValue(TheTextProperty, value);
}
}
public SomeControl()
{
InitializeComponent();
DataContext = this;
}
}
// Window ----
public partial class Window1 : Window
{
private readonly MyClass _myClass;
public Window1()
{
InitializeComponent();
_myClass = new MyClass();
_myClass.MyStringProp = "Hallo Welt";
DataContext = _myClass;
}
}
public class MyClass// : DependencyObject
{
// public static DependencyProperty MyStringPropProperty = DependencyProperty
// .Register("MyStringProp", typeof (string), typeof (MyClass));
public string MyStringProp { get; set; }
// {
// get { return (string)GetValue(MyStringPropProperty); }
// set { SetValue(MyStringPropProperty, value); }
// }
}
Best RegardsOliver Hanappi
PS: I've tried to implement the INotifyPropertyChanged interface on my user control, but it did not help.
You want to bind the Text property of your TextBox back to the TheText property of the UserControl it lives in, right? So you need to tell the binding where the property lives. There's a couple of ways to do this (you can do it with a RelativeSource using FindAncestor) but the easiest way is to give the UserControl a "name" in the XAML and bind using element binding:
<UserControl ...
x:Name="me" />
<TextBox Text="{Binding TheText,ElementName=me}" />
</UserControl>
Now your TextBox will reflect the value you've assigned (or bound) to your "SomeControl.TheText" property - you needn't change any of your other code, although you'll probably want to implement INotifyPropertyChanged on your underlying MyClass object so that the binding knows when the property has changed.
Matt has provided a solution to your problem. Here is a little more explanation and a hint to stop this problem in future.
As SomeControl.DataContext is set in the SomeControl constructor, the window's binding TheText="{Binding Path=MyStringProp}" has a Source of type SomeControl, not MyClass as you intended.
Any bindings that fail at runtime cause debug messages to be logged to the output panel of Visual Studio. In this case, you would have seen that no such property 'MyStringProp' exists on object of type 'SomeControl', which should have raised your suspicions.
I think everyone finds WPF data binding takes some time to learn and especially to debug, but persevere. Data binding in WPF is really fantastic, and I still get a kick out of knowing how easily it makes the data on my UIs stay up to date.