Hi i have 2 listviews in a grid. Each listview contains a textbox and both textboxes in both listviews have the same text. When i select part of the text in either textbox, the other textbox will select the same part of the text as well.
can binding between two textbox in 2 different listview be done?
As AngelWPF writes, the Selection*-properties are not dependency properties so you cannot use databinding on them.
What you can do though, is to add your own subcalss of the TextBox that has dependency properties that replace the original properties. These can be implemented as regular dependency properties using the same names as the original properties, but the definition of them must be public new to replace the originals.
I will not post an entire code sample here (too much code and I don't have it on this computer), but you can do something like the following:
public class BindableSelectionTextBox : TextBox
{
// Defines the dependency property as normal
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.RegisterAttached(SelectedText, typeof(string),
typeof(BindableSelectionTextBox),
new FrameworkPropertyMetadata("", SelectedTextPropertyChanged));
private static void SelectedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = (TextBox)d;
textBox.SelectedText = (string)e.NewValue;
}
public new string SelectedText
{
get { return (string)GetValue(SelectedTextProperty); }
set
{
if(value != SelectedText)
{
SetValue(SelectedTextProperty, value);
}
}
}
public BindableSelectionTextBox()
{
SelectionChanged += OnSelectionChanged;
}
private void OnSelectionChanged(object sender, RoutedEventArgs e)
{
SelectedText = base.SelectedText;
}
}
Now, you must repeat this for the SelectionStart and SelectionLength properties and you should be done.
Sadly because SelectionText, SelectionLength and SelectionStart are not dependency properties, the two textboxes canot be two way bound on these properties.
You will have to write an attached behavior, attach one textbox to another and handle TextBox.SelectionChangedEvent for both, and synchronize the other when the event is handled.
Related
I have a class that contains a property that is an enum:
public RaTypes RaBucket1Type { get; set; }
My enum is:
public enum RaTypes
{
Red,
Yellow
}
I was able to bind a form's combobox data-source to the enum so that when I click on the drop-down, I see the enumerations:
cmbBucket1Type.DataSource = Enum.GetValues(typeof(RaTypes));
When I load the form, I would like to populate the combo-box with the existing value. I have tried the following:
cmbBucket1Type.DisplayMember = "TradeType";
cmbBucket1Type.ValueMember = "TradeEnumID";
cmbBucket1Type.SelectedValue = EditedAlgorithm.RaBucket1Type;
But this did not work.
Also, I'm not sure I have implemented the ValueChanged event handler correctly either:
EditedAlgorithm.RaBucket1Type = (RaTypes)((ComboBox)sender).SelectedItem;
Can someone help me understand:
How to set the combobox to current value, and
How to handle the event handler so I can set the property to whatever was selected?
Thanks
-Ed
UPDATES
I have tried
cmbBucket1Type.SelectedIndex = cmbBucket1Type.FindString(EditedAlgorithm.RaBucket1Type.ToString());
and
cmbBucket1Type.SelectedItem = EditedAlgorithm.RaBucket1Type;
Neither works.
I think you're using the terminology a little differently than normal, which makes it difficult to understand.
Normally, the terms Add, Populate, and Select are used to mean the following:
Add - Add an item to the existing set of items in the combo box.
Populate - Initialize the combo box with a set of items.
Select (Display) - Choose one among many items in the combo box as the selected item. Normally this item will be displayed in the combo box visible area.
Having cleared that up, I assume following is what you want to do.
Initially populate the ComboBox with a set of values. In your case, values of RaType Enum.
Create an instance of your class which contains the property mentioned. Since you didn't name that class I'll simply name it SomeClass.
Initialize the RaBucket1Type property of the said class instance with an enum value of your choice. I'll initialize it to Yellow.
Have the ComboBox select the said value at start up.
After Form_Load, at any given time, if the user changes the value of the ComboBox, have the change reflected in your class instance property.
For that, I would do something like this:
public partial class MainForm : Form
{
// Your class instance.
private SomeClass InstanceOfSomeClass = null;
public MainForm()
{
InitializeComponent();
// Initialize the RaBucket1Type property with Yellow.
InstanceOfSomeClass = new SomeClass(RaTypes.Yellow);
// Populating the ComboBox
comboBox1.DataSource = Enum.GetValues(typeof(RaTypes));
}
// At selected index changed event
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
// Get the selected value.
var selected = comboBox1.SelectedValue;
// Change the `RaBucket1Type` value of the class instance according to the user choice.
InstanceOfSomeClass.RaBucket1Type = (RaTypes)selected;
}
private void MainForm_Load(object sender, EventArgs e)
{
// At form load time, set the `SelectedItem` of the `ComboBox` to the value of `RaBucket1Type` of your class instance.
// Since we initialized it to `Yellow`, the `ComboBox` will show `Yellow` as the selected item at load time.
if (InstanceOfSomeClass != null)
{
comboBox1.SelectedItem = InstanceOfSomeClass.RaBucket1Type;
}
}
}
public enum RaTypes
{
Red,
Yellow
}
public class SomeClass
{
public RaTypes RaBucket1Type { get; set; }
public SomeClass(RaTypes raTypes) { RaBucket1Type = raTypes; }
}
Please do keep in mind this is a basic example to show you how to handle the situation and not a complete finished code. You'll need to do a bunch of error checks to make sure class instances and selected items are not null etc.
I FOUND MY ANSWER:
I had the SelectedIndexChanged event pointing to my event handler which means that when I "added" items to the ComboBox using:
comboBox1.DataSource = Enum.GetValues(typeof(RaTypes));
it was triggering the event handler, and resetting my class property. My event handler was this:
var selectedValue = cmbBucket1Type.SelectedValue;
So the simple solution was to:
Remove the hard-coded event handler from the Visual Studio GUI.
Add the following event handler in code AFTER I assign the DataSource
bucketType1.SelectedIndexChanged += BucketTypeChanged;
This worked.
THANK YOU ALL FOR HELPING!!
-Ed
You can set the selectedValue like this:
cmbBucket1Type.SelectedValue = EditedAlgorithm.RaBucket1Type;
And you can handle the selected value when the combo change like this:
private void cmbBucket1Type_SelectedValueChanged(object sender, EventArgs e)
{
var selectedValue = cmbBucket1Type.SelectedValue;
}
so I have a model which contains 2 variables, a List and a DateTime. In my UserControl I have a DependencyProperty and I also defined a PropertyChangedCallback.
public static readonly DependencyProperty MyProperty = DependencyProperty.Register("My", typeof(List<MyContainer>), typeof(UC), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnMyProperty)));
public List<MyContainer> My
{
get
{
return GetValue(MyProperty) as List<MyContainer>;
}
set
{
SetValue(MyProperty, value);
}
}
private static void OnMyProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UC control = d as UC;
//do stuff
}
On my form there is a button, which do the changes on the other model variable (on the DateTime).
private void Date_Click(object sender, RoutedEventArgs e)
{
MyModel model = DataContext as MyModel;
if (model != null)
{
model.Date = model.Date.AddDays(1);
}
}
And finally here is my model.
public class MyModel : INotifyPropertyChanged
{
private List<MyContainer> _My;
private DateTime _Date;
public MyModel()
{
_Date = DateTime.Now.Date;
_My = new List<MyContainer>();
}
public List<MyContainer> My
{
get
{
return _My;
}
set
{
_My = value;
OnPropertyChanged("My");
}
}
public DateTime Date
{
get
{
return _Date;
}
set
{
_Date = value;
OnPropertyChanged("Date");
OnPropertyChanged("My");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
XAML declaration is the following.
<local:UC My="{Binding My}" />
So my problem is the after I hit the run, it fires the OnMyProperty once, after that if I hit the button, it changes the DateTime property well, but the OnMyProperty callback doesn't firing again. However I noticed that if I modify my model like this
public DateTime Date
{
get
{
return _Date;
}
set
{
_Date = value;
_My = new List<MyContainer>(_My); //added
OnPropertyChanged("Date");
OnPropertyChanged("My");
}
}
now it fires it every time when I hit the button. How can I trigger the second behaviour without that modification?
After setting the value of a DependencyProperty it first checks if the new value is different to the old one. Only in this case the PropertyChangedCallback method you registered with that DependencyProperty is called. So the name PropertyChanged makes sense.
In your (not modified) case you not even try to change My (only Date). So there is no reason to raise the callback function.
The answer is that you almost certainly do not need to do this. When you ask a question about how to make the framework do something it really does not want to do, always say why you think you need to do that. It's very likely that there's a much easier answer that everybody else is already using.
The only thing you have bound to the control is My. Therefore, if My hasn't changed, then the state of the control should not change. If you want the state of the control to change when Date changes, bind Date to some property of the control. The only way the control should ever get information from any viewmodel is through binding one of its dependency properties to a property of the viewmodel.
The control should not ever know or care who or what is providing values for its properties. It should be able to do its job knowing only the property values it has been given.
If the contents of My have changed -- you added an item or removed one -- of course the control has no way of knowing that, because you refused to tell it. You're just telling it there's a new list. It checks, sees it's still got the same old list, and ignores you. The My property of your viewmodel should be an ObservableCollection, because that will notify the control when you add or remove items in the collection.
The items themselves, your MyContainer class, must implement INofityPropertyChanged as well, if you want to be able to change their properties while they are displayed in the UI.
The dependency property My on your control must not be of type List<T>. It should probably be type object, just like ItemsControl.ItemsSource. Then your control template can display it in an ItemsControl which knows what to do with it. If an ObservableCollection is bound to it as I suggested above, the ItemsControl will update automatically. In OnMyProperty, your control class can check to see if it's an observable collection as well:
private static void OnMyProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UC control = d as UC;
if (e.NewValue is INotifyCollectionChanged)
{
(e.NewValue as INotifyCollectionChanged).CollectionChanged +=
(s, ecc) => {
// Do stuff with UC and ecc.NewItems, ecc.OldItems, etc.
};
}
}
I'm trying to replace the SelectedText of a TextBox with a new value by binding on the custom property 'Selected'. Currently, updating Selected through binding doesn't change the actual SelectedText. I'm almost there I think; at least mouse-selecting text is updating Selected.
I'd prefer solutions based on inheriting from TextBox if possible.
Can anybody tell me what's missing please?
class SelectionTextbox : TextBox
{
public static readonly DependencyProperty SelectionProperty = DependencyProperty.Register("Selection", typeof(string), typeof(SelectionTextbox));
public string Selection
{
get
{
return (string)base.GetValue(SelectionProperty);
}
set
{
base.SetValue(SelectionProperty, value);
}
}
protected override void OnSelectionChanged(RoutedEventArgs e)
{
base.OnSelectionChanged(e);
Selection = SelectedText;
}
}
The problem is, that you never actually do anything with the value you assign to Selection. You need to actually make it the selected text.
public string Selection
{
get
{
return (string)base.GetValue(SelectionProperty);
}
set
{
base.SetValue(SelectionProperty, value);
if(value != SelectedText)
SelectedText = value;
}
}
For Binding to update the source you have to specify Mode=TwoWay if you want to reflect changes back to code. This can be done by two ways:
Selection="{Binding Path=MyProperty, Mode=TwoWay}"
or by
public static readonly DependencyProperty SelectionProperty =
DependencyProperty.Register("Selection",
typeof(string),
typeof(SelectionTextbox),
new FrameworkPropertyMetadata(default(string),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
By using the second method you have that all bindings to Selection are done TwoWay and you do not have to specify it explicitly.
I am trying to create a very simple WPF User Control to represent a digital clock.
I have a couple of things I want client code to be able to change, e.g. foreground text colour, font etc., so I've made some public properties for them. Some of the code is shown below:
public partial class DigitalClock : System.Windows.Controls.UserControl
{
public string Color { get; set; }
private Timer timer;
private string DisplayString { get { return DateTime.Now.ToString("dd-MM-yy HH:mm:ss"); } }
public DigitalClock()
{
InitializeComponent();
this.timer = new Timer();
this.timer.Tick += new EventHandler(UpdateClock);
this.timer.Interval = 1000;
this.timer.Enabled = true;
this.timer.Start();
UpdateClock(null, null);
try
{
//exception thrown here as this.Color is null
Color color = (Color)ColorConverter.ConvertFromString(this.Color);
tbClock.Foreground = new SolidColorBrush(color);
}
catch (Exception ex)
{
Console.WriteLine(">>>" + ex.Message);
}
}
private void UpdateClock(object sender, EventArgs e)
{
tbClock.Text = DisplayString;
}
}
}
I'm using it on another page like this:
<CustomControls:DigitalClock color="#ff000000" />
There are no syntax errors and the clock appears on the screen, but whenever the code hits the line where it's trying to set the colour, I just get an Object reference is not set to an instance of an object.
I assume this is something to do with the point in time at which the Color property is set, since after the first 'tick' of the timer, the value is no longer null. How do I get around this?
When you insert your control inside another XAML document, the properties that are set from this document will be set after your control is intantiated, which means at the time your constructor is invoked the Colorproperty that you may have set in your other XAML document still has its default value.
To do what you want you can either:
Listen for the Loaded event of your control (see https://msdn.microsoft.com/en-us/library/ms742302%28v=vs.110%29.aspx), this will be invoked after all the properties of the control instance are set (you may want to start your timer here, and stop it in the Unloaded event to make sure it doesn't tick when the control isn't instantiated on screen),
You can write a body for the setter of your Color property to propagate the change:
public string Color
{
set { tbClock.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString(value)); }
}
If you want to set the color from another XAML document, you can also provide a property of type Brush instead of color:
public Brush ClockForeground
{
get { return tnClock.Foreground; }
set { tnClock.Foreground = value; }
}
So that in your other XAML document, you can set the color directly by letting the XAML parser translate the color name into a brush automatically:
<local:DigitalClock ClockForeground="Yellow" />
Or better, you can declare a dependency property on your control and use data binding (assuming here tbClock is a TextBlock):
public Brush ClockForeground
{
get { return (Brush)GetValue(ClockForegroundProperty); }
set { SetValue(ClockForegroundProperty, value); }
}
public static readonly DependencyProperty ClockForegroundProperty = DependencyProperty.Register("ClockForeground", typeof(Brush), typeof(DigitalClock));
public DigitalClock()
{
InitializeComponents();
...
BindingOperations.SetBinding(tbClock, TextBlock.ForegroundProperty, new Binding
{
Source = this,
Path = new PropertyPath(ClockForegroundProperty)
});
}
You should not declare properties as CLR properties. You should create instead Dependency Properties which in default allows you binding, validation and many, many more. Check this out: http://www.codeproject.com/Articles/32825/How-to-Creating-a-WPF-User-Control-using-it-in-a-W. In your example handle event Loaded like this:
this.Loaded += DigitalClock_Loaded;
void DigitalClock_Loaded(object sender, RoutedEventArgs e)
{
//your actions
Color color = (Color)ColorConverter.ConvertFromString(this.Color);
}
Properties is not yet bound in constructor.
Instead of asigning the color in the constructor, since it will be always null because the property will not be set until object is instantiated, it seems better to use the setter of the Color property, using a backing field.
Also, note that you should use Dependency Properties in this case, and take advantage of binding.
Let's say I have a String property in my form (and the form does not implement INotifyPropertyChanged). I've also created a BindingSource and set its DataSource to the form. Then I bind a textbox to the String property on my form (indirectly, using the BindingSource).
Question 1: When I change the value in the textbox at runtime, why don't I hit a breakpoint in the setter of the String property? I thought that binding the control to the String property would allow updates in this direction (GUI -> member data) to occur automatically
Question 2: How can I trigger updates in the other direction (member data -> GUI) to occur when something other than the GUI changes the String property? I don't want to implement the INotifyPropertyChanged interface and add NotifyPropertyChanged to the setter. I thought that by using the BindingSource's ResetBindings I could at least trigger this manually
public partial class Form1 : Form
{
private String m_blah;
public String Blah
{
get
{
return m_blah;
}
set
{
m_blah = value;
}
}
public Form1()
{
InitializeComponent();
textBox1.DataBindings.Add(new Binding("Text", bindingSource1, "Blah",true,DataSourceUpdateMode.OnValidation));
}
private void button1_Click(object sender, EventArgs e)
{
Blah = "Clicked!";
this.bindingSource1.ResetBindings(false); //expecting the GUI to update and say "Clicked!"
}
}
this.bindingSource1.DataSource = this;
I think you forget to assign data source.