I am using MVVM/WPF and trying to do something seemingly simple, but cant find a clean solution.
I want to do the following:
When a property changes in the model (WPF Textbox text would be changed in this case), use a method to perform other operations on the UI relating to the property bound.
Currently i am using a multibinding on the tooltip (to get the textbox datacontext + binding path), but this is a bit of a hack.
<TextBox x:Name="textBox" Text="{Binding Model.MyProperty}">
<TextBox.ToolTip>
<MultiBinding Converter="{StaticResource brNewMultiConverter}">
<!-- This to trigger the converter in all required cases.
Without it, i cant get the event to fire when filling
the model initially
-->
<Binding ElementName="textBox" Path="Text" />
<!-- This has the properties i need, but wont fire without
the binding above -->
<Binding ElementName="textBox" />
</MultiBinding>
</TextBox.ToolTip>
</TextBox>
I would like to make something re-usable and maybe for different controls, hence i am not just using the textchanged event.
If anyone could point me in the right direction, it would be much appreciated.
OK, so far as your Multibinding there, what are you trying to accomplish there? I don't know what your converter is supposed to do, but can it not be done with an IValueConverter implementing class? I am assuming not, it looks like you are passing the textbox to the converter.
As far as having a method do several things when your model properties get updated, you can have the viewmodel subscribe to events on your model class. Just declare the object WithEvents (VB.NET) and add event handlers for On[PropertyName]Changed.
When implementing MVVM, I tend to treat the codebehind as a second class citizen. I do my best to push all logic off to the ViewModel or View if I can. I have almost completely stopped using Converters as much of that logic can be duplicated in ViewModels, and if it is something that I want to re-use, I usually just have a little helper class that gets whatever passed to it, does something, and passes it back out. I have never really had that great a relationship with IValueConverter...
Other than that, it is unclear exactly what you are trying to do. Could we get some more clarification?
It looks like you're trying to have the tooltip have the content of the textbox, if so why not just do this?
<TextBox Text="{Binding Model.MyProperty}" ToolTip="{Binding Model.MyProperty}"/>
If that's not what you want, but want the tooltip to change based on the value of the textbox then do that in your viewmodel e.g.
public class MyViewModel
{
string _MyProperty;
public string MyProperty
{
get { return _MyProperty;}
set
{
_MyProperty = value;
OnPropertyChanged("MyProperty");
OnPropertyChanged("MyToolTipProperty"); //force WPF to get the value of MyToolTipProperty
}
}
public string MyToolTipProperty
{
get
{
//return what you want
}
}
}
and then in your markup:
<TextBox Text="{Binding Model.MyProperty}" ToolTip="{Binding Model.MyToolTipProperty}"/>
Related
How can I bind a Text property for my TextBox that read from a source but it will store its value to a different target?
Let's say
I have a textbox which is bond to a path in a CollectionViewSource
<Window>
<Window.Resources>
<CollectionViewSource Source="{Binding Source={StaticResource ProgramView}, Path='FK_LevelList_ProgramList'}" x:Key="LevelLookupView" />
</Window.Resources>
<TextBox Name="FeePerTermTextbox" Text="{Binding Source={StaticResource LevelLookupView}, Path='FeePerTerm', Mode=OneWay, StringFormat=c2}"/>
</Window>
When perform save, the value of the TextBox will store to another model that is different from the CollectionViewSource
Thanks
I consider this flawed. What happens if the source gets updated? Should the textbox be overwritten?
The reason for this design is imho, that the UI should reflect the "traits" of the element set as DataContext, therefore i expect it to contain the value i give in the model or in the ui. Now there is of course nothing stopping you from not writing the value in your viewmodel to your model, when receiving the set value from the textbox.
public class Redirecter
{
public string FileName
{
get{return mModel.FileName;}
set{mProxy.FileName = value;}
}
}
But this of course won't work well together with INotifyPropertyChanged. I would use a different approach. Use a model that reflects your ui more. If you open the view fill in this ui model with your settings from model A. If you now save this, save each property into Model B.
I'm using EntityFramework, WPF and MVVM in my application and got some problems with updating the databinding of relationships between EntityObjects. I was able to downsize my problem to only a few lines of XAML and I hope someone can help me as I'm still not very confident with EF and MVVM.
Anyway, here we go with the simplified XAML:
<DatePicker Grid.Row="2" Grid.Column="1"
SelectedDate="{Binding Path=File.SentDate,
StringFormat={}{0:dd/MM/yyyy}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center" IsEnabled="{Binding Path=IsEnabled}"/>
<ComboBox Grid.Row="3" Grid.Column="1" ItemsSource="{Binding Contacts}" DisplayMemberPath="Name"
SelectedItem="{Binding Path=File.Sender, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEditable="True"
VerticalAlignment="Center">
</ComboBox>
<Label Content="{Binding Path=File.SenderId}" Grid.Row="4"/>
<Label Content="{Binding Path=File.Sender.Name}" Grid.Row="5"/>
<Label Content="{Binding Path=File.SentDate}" Grid.Row="6"/>
I'm using the last 3 Labels to test my databinding. Changing the File.SentDate using the DatePicker updates the databinding to the last Label without problem.
Now File is of type EntityObject and has a SenderId property of type GUID. It also has a relationship to my Contacts through the Sender property. Obvisouly, SenderId is the GUID of the corresponding Contact EntityObject which is related to File through the Sender relationship. A File can have only 1 single Sender of type Contact.
Anyway, what happens is that when I select another sender using the combobox, the Label displaying the File.SenderId property get properly updated. However, the one with the File.Sender.Name property i.e. the one using the reléationship does not get updated.
So I'm guessing that there is something special about updating the databinding of relationships in EF.
Can someone please suggest a solution to this?
Unfortunately, the Entity Framework doesn’t notify when an association property changes. That’s the reason why your Binding didn’t work.
The issue is reported to Microsoft: http://connect.microsoft.com/VisualStudio/feedback/details/532257/entity-framework-navigation-properties-don-t-raise-the-propertychanged-event
Another workaround is shown by the BookLibrary sample application of the WPF Application Framework (WAF). The Book class listens to the AssociationChanged event and raises the appropriate PropertyChanged event.
public Book()
{
…
LendToReference.AssociationChanged += LendToReferenceAssociationChanged;
}
private void LendToReferenceAssociationChanged(object sender,
CollectionChangeEventArgs e)
{
// The navigation property LendTo doesn't support the PropertyChanged event.
// We have to raise it ourselves.
OnPropertyChanged("LendTo");
}
Looks like I've found a solution, though to me its more like a workaround. It's not the solution I
would have expected but it works.
The XAML is still the same as above, except for one thing. Instead of binding to File.Sender.Name, I bind to File.SenderName like this:
<Label Content="{Binding Path=File.SenderName}" Grid.Row="4"/>
SenderName in this case is a property of the object File which I added in a partial class like this:
public partial class File
{
public string SenderName
{
get
{
if (this.Sender != null)
{
return this.Sender.Name;
}
return string.Empty;
}
}
protected override void OnPropertyChanged(string property)
{
if (property == "SenderId")
{
OnPropertyChanged("SenderName");
}
base.OnPropertyChanged(property);
}
}
So what happens here is that if the SenderId property is changed, I tell the framework to also update the SenderName property. That's it. Works like a charm. Although I'm still not convinced that this is the way it is supposed to work.
Another workaround if you simply want a name is to overide ToString() for the Sender and bind directly to sender. This workaround is good because most of the time when we are databinding to Property of a Property we do it in order to get a "name" of object set as property value. Also this method works for Database First approach too if you edit tt files to add partial to all class definitions.
So you add a file to contain ToString extensions of your Entites and in it you add something like this:
public partial Contacts
{
public override string ToString()
{
return Name;
}
}
so you can databind
<Label Content="{Binding Path=File.Sender}" Grid.Row="5"/>
Now the databinding will detect if the Sender changes, and when it does it will call ToString to determine what to display.
On the other hand if you need to bind to another non standard property you might have problems. I do remember having success with using DataContext and templates to get around it. You bind to Sender and use DataTemplate to determine what to display.
Normally when you want a databound control to 'update,' you use the "PropertyChanged" event to signal to the interface that the data has changed behind the scenes.
For instance, you could have a textblock that is bound to the datacontext with a property "DisplayText"
<TextBlock Text="{Binding Path=DisplayText}"/>
From here, if the DataContext raises the PropertyChanged event with PropertyName "DisplayText," then this textblock's text should update (assuming you didn't change the Mode of the binding).
However, I have a more complicated binding that uses many properties off of the datacontext to determine the final look and feel of the control. To accomplish this, I bind directly to the datacontext and use a converter. In this case I am working with an image source.
<Image Source="{Binding Converter={StaticResource ImageConverter}}"/>
As you can see, I use a {Binding} with no path to bind directly to the datacontext, and I use an ImageConverter to select the image I'm looking for. But now I have no way (that I know of) to tell that binding to update. I tried raising the propertychanged event with "." as the propertyname, which did not work.
Is this possible? Do I have to wrap up the converting logic into a property that the binding can attach to, or is there a way to tell the binding to refresh (without explicitly refreshing the binding)?
Any help would be greatly appreciated.
Thanks!
-Adam
The workaround here was to add a property to my object (to be used as the datacontext) called "Self" , which simply returned
public Object Self { get { return this; }}
Then in the binding I used this property:
<Image Source="{Binding Path=Self, Converter={StaticResource ImageConverter}}"/>
Then when I call
PropertyChanged(this, new PropertyChangedEventArgs("Self"))
it works like a charm.
Thanks all.
I don't believe there is a way of accomplishing exactly what you need with your current converter. As you mentioned, you could do the calculation in your ViewModel, or you could change your converter into an IMulitValueConverter.
From your specific scenario (the converter tied to a ViewModel class, and a few of its properties), I would lean towards implementing the logic in the ViewModel.
Hmm, you don't show the full implementation. But I think it should update, if the value bound to the GUI provides the PropertyChanged-Event.
Regards
I am trying to figure out the best way to select all the text in a TextBox the first time the control is loaded. I am using the MVVM pattern, so I am using two-way binding for the Text property of the TextBox to a string on my ViewModel. I am using this TextBox to "rename" something that already has a name, so I would like to select the old name when the control loads so it can easily be deleted and renamed. The initial text (old name) is populated by setting it in my ViewModel, and it is then reflected in the TextBox after the data binding completes.
What I would really like to do is something like this:
<TextBox x:Name="NameTextBox" Text="{Binding NameViewModelProperty, Mode=TwoWay}" SelectedText="{Binding NameViewModelProperty, Mode=OneTime}" />
Basically just use the entire text as the SelectedText with OneTime binding. However, that does not work since the SelectedText is not a DependencyProperty.
I am not completely against adding the selection code in the code-behind of my view, but my problem in that case is determining when the initial text binding has completed. The TextBox always starts empty, so it can not be done in the constructor. The TextChanged event only seems to fire when a user enters new text, not when the text is changed from the initial binding of the ViewModel.
Any ideas are greatly appreciated!
Dan,
I wrote a very simple derived class, TextBoxEx, that offers this functionality. The TextBoxEx class derives from TextBox, and can be referenced in XAML for any and all of your TextBox’s. There are no methods to call. It just listens for Focus events and selects it own text. Very simple.
Usage is as follows:
In XAML, reference the assembly where you implement the TextBoxEx class listed below, and add as many TextBoxEx elements as you need. The example below uses data binding to display a username.
<UserControl x:Class="MyApp.MainPage"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:ClassLibrary;assembly=ClassLibrary"
>
.
.
.
<c:TextBoxEx x:Name="NameTextBox" Text="{Binding NameViewModelProperty, Mode=TwoWay}" Width="120" />
This code below works with Silverlight 3.
using System.Windows;
using System.Windows.Controls;
namespace ClassLibrary
{
// This TextBox derived class selects all text when it receives focus
public class TextBoxEx : TextBox
{
public TextBoxEx()
{
base.GotFocus += OnGotFocus;
}
private void OnGotFocus(object sender, RoutedEventArgs e)
{
base.SelectAll();
}
}
}
Good luck.
I'm leaving Jim's solution as the answer, since calling SelectAll() on the GotFocus event of the TextBox did the trick.
I actually ended up making a Blend TriggerAction and an EventTrigger to do this instead of subclassing the TextBox or doing it in code-behind. It was really simple to do and nice to be able to keep the behavior logic encapsulated and just add it declaratively in XAML to an existing TextBox.
Just posting this in case anyone else comes across this thread and is interested:
XAML:
<TextBox x:Name="NameTextBox" Text="{Binding NameViewModelProperty, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<local:SelectAllAction/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
C#
public class SelectAllAction : TriggerAction<TextBox>
{
protected override void Invoke(object parameter)
{
if (this.AssociatedObject != null)
{
this.AssociatedObject.SelectAll();
}
}
}
Just wanna add a link I found pertaining to this - here is a fantastic discussion (read comments) on Behaviours vs subclassing vvs attached properties...
I understand that Silverlight 3.0 has binding but just want a simple example on how to use this to read a property from a class.
I have a class called Appointment which as a String property called Location:
Public Property Location() As String
Get
Return _Location
End Get
Set(ByVal Value As String)
_Location = Value
End Set
End Property
With a Private Declaration for the _Location as String of course.
I want a XAML element to bind to this property to display this in a TextElement, but it must be in XAML and not code, for example I want something like this:
<TextBlock Text="{Binding Appointment.Location}"/>
What do I need to do to get this to work?
It has to be a Silverlight 3.0 solution as some WPF features are not present such as DynamicResource which is what I'm used to using.
Just to add that my XAML is being loaded in from a seperate XAML File, this may be a factor in why the binding examples don't seem to work, as there are different XAML files the same Appointment.Location data needs to be applied.
You have two options.
If the "Appointment" class can be used as the DataContext for the control or Window, you can do:
<TextBlock Text="{Binding Location}" />
If, however, "Appointment" is a property of your current DataContext, you need a more complex path for the binding:
<TextBlock Text="{Binding Path=Appointment.Location}" />
Full details are documented in MSDN under the Binding Declarations page. If neither of these are working, make sure you have the DataContext set correctly.
You need something in code, unless you want to declare an instance of Appointment in a resource and bind to that but I doubt thats what you want.
You need to bind the Text property to the Property Path "Location" then assign the DataContext of the containing XAML to an instance of the Appointment:-
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Text="{Binding Location}" />
</Grid>
Then in the control's load event:-
void Page_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new Appointment() { Location = "SomePlace" };
}
Note in this case I'm using the default Page control.
If I'm reading correctly, you need to create an instance of Appointment, set the DataContext of the control to that instance and modify your binding to just say: Text="{Binding Location}"
Also, consider implementing INotifyPropertyChanged on your Appointment class to allow the data classes to notify the UI of property value changes.