In my MVVM test project I want to bind my textbox to the object from viewmodel:
public class ContactViewModel : BaseNotifyPropertyChanged
{
Contact _selectedItem;
public ContactViewModel()
{
ContactModel contactModel = new ContactModel();
_selectedItem = contactModel.ContactList[1]; // this contains first contact from the list;
}
}
public Contact SelectedContact
{
get
{
return _selectedItem;
}
}
in my Contact class I am overriding ToString Method in order to show first Contact's first name:
public override string ToString()
{
return _firstName.ToString();
}
And here is my XAML binding:
<TextBox Height="23" HorizontalAlignment="Left" Name="SelectedItemTextBox" Text="{Binding Path=SelectedContact}" VerticalAlignment="Top" Width="120" />
And for some reason this textbox is always empty. However, if I change
public String SelectedContact
{
get
{
return _selectedItem.LastName;
}
}
it works perfectly.
Stanislav, you did a mistake in other place. You try to bind to object, binding doesn't know what to show and apply ToString() to your Contact object. If you overrode ToString(), it had to show a returned value of this method. I created the test app, and it works in this way!
What I can see in your code, in ToString() you return FirstName, but in changed SelectedContact it is SecondName - did you fill first name before?
You wrote in comment that tried to access to first element, but in code you take second element of ContactList
Moreover, use binding in this way is incorrect. If you want to access to LastName use next way:
<TextBox Text="{Binding Path=SelectedContact.LastName, Mode=OneTime}" />
And remove ToString() overriding.
EDIT: Unlike to other controls where binding is OneWay by default in TextBox it is TwoWay by default. It was done because native behavior of TextBox is show and edit value (not only show as in other controls). Moreover if you don't plan to change value (you don't plan, because ContactModel doesn't implement INotifyPropertyChanged) it is recommended to use OneTime mode (for performance).
TwoWay has some restriction - you can't use it for read-only property (SelectedContact is read-only in your code). Because binding can't change the value in this case - make sense. It is strange that app lunched in your case and TextBox was empty, because in my case I get the error "A TwoWay or OneWayToSource binding cannot work on the read-only property 'SelectedContact' of type 'WpfApplication1.ContactViewModel'." until I changed binding mode in TextBox.
I guess you followed this Article on MSDN:
http://msdn.microsoft.com/en-us/library/ms742521%28v=vs.110%29.aspx
Alltough the article says, that the standard representation of a ListBox is a List of ToString representation of its contents, this is not the case for every other element.
I would highly recommend to create a DataBinding Template for you Contact class, it's a much cleaner way to implement this behaviour than overriding ToString.
Ah, found it, I just had to change my TextBox to the TextBlock and now everything works properly !
It seems like TextBlock does understand how to show objects, but TextBox doesn't.
Related
Got a tough one. Consider a ViewModel that is comprised of a list of objects, where each object defines an int value, and some of those objects also define a Presets dictionary of ints keyed on a 'friendly' string representing that value in the UI.
Here's an example...
List<SomeItem> AllItems;
public class SomeItem : INotifyPropertyChanged
{
public SomeItem(int initialValue, Dictionary<int,string> presets)
{
this.CurrentValue = initialValue;
this.Presets = presets;
}
public int CurrentValue{ get; set; } // Shortened for readability. Assume this property supports INPC
public Dictionary<int,string> Presets{ get; private set; }
}
The goal for the UI is if the item has no presets, the user can enter any int value they want. However, if there are presets, we want to limit them to those values and also display them in the UI as the Friendly names from the dictionary.
Our first attempt was to use a TextBox and a ComboBox, modifying their visibilities depending on if there were presets or not, like this...
<ComboBox ItemsSource="{Binding Presets}"
DisplayMemberPath="Key"
SelectedValuePath="Value"
SelectedValue="{Binding CurrentValue, Mode=TwoWay}"
Visibility={Binding HasPresets, Converter=...}">
<TextBox Text="{Binding CurrentValue}"
Visibility={Binding HasPresets, Converter...}" /> // Assume the inverse of the combo
...but when we're using this in a DataTemplate of a list that supports virtualization, the combo occasionally displays blanks. I believe is because when the item is reused and the DataContext changes, SelectedValue updates before ItemsSource meaning there's potentially no preset value to match on, thus the proposed SelectedValue value gets tossed by the control, then ItemsSource updates, but there's no selected value so it shows a blank.
My next thought (and what we preferred anyway) was to use only a TextBox that displayed the preset name but was actually bound to Value, then use a converter to work its magic, and let the user type either the friendly name or the actual value. If the user typed something that wasn't a valid value or a preset, we'd just throw an error. If there were no presets, it would simply act as a pass-through of the value.
However, there I'm not sure how to pass in the presets to the converter. You can't set a binding on a ConverterParameter to pass them in that way, and if you use a multi-binding, then I'm not sure how to structure the 'ConvertBack' call since there too I need them passed in, not sent back out.
I'm thinking the proper way is to implement UiValue in the ViewModel which we'd simply bind to like this...
<TextBox Text="{Binding UiValue}" />
...then move the code that would've been in the converter to that property's getter/setter implementation, or simply be a pass-through to Value if there are no presets. However, this seems like too much logic is going in the ViewModel where it should be in the View (ala a converter or similar.) Then again, maybe that's exactly the point of the ViewModel. I dunno. Thoughts welcome.
Personally, I would go for putting the 'converter code' into the property as you suggested... I don't see any problem with having the code in there. In fact, it's probably better than having it in a Converter because then you can easily test it too.
Sorry, this isn't much of an answer, but I felt that your question deserved at least one.
I like your question, because it illustrates the way of thinking that stands behind the existence of a ViewModel in WPF. Sometimes they seem inevitable.
Converters are designed to be stateless, which is why it's difficult to pass context variables like presets. ViewModel is a layer, of which responsibility is to prepare Model for binding purposes. The role of a "model" is to handle logic. Thus, a ViewModel may handle in detail the behaviour (logic) of a View. This is precisely what you want. Most of the time I find myself not needing Converters at all.
Sometimes it feels more natural that the view logic should be in the View, but then ViewModel seems superfluous. However, when that logic is located in the ViewModel it's usually easier to auto-test. I wouldn't be afraid of putting stuff like this in ViewModel at all. Often this is the easiest (and correct) way.
Have UiValue property in ViewModel and handle conversion there:
public string UiValue{ get{/*...*/} set{/*...*/} }
To rephrase, in WPF there is no clean way to replace the property you bind to. E.g. if you wanted to have
<TextBox Text="{Binding IntValue}" />
change at some point to:
<TextBox Text="{Binding PresetValue}" />
you're trapped. This is not how things are done. Better have a constant binding like
<TextBox Text="{Binding UiValue}" />
and deal with the logic behind the UiValue property.
Another possible approach (instead of playing with visibility of ComboBox and TextBox) is to have a DataTemplateSelector, which would decide whether a ComboBox or TextBox should be created for SomeItem. If presets are null or empty select TextBox-based DataTemplate, otherwise take ComboBox. If I'm not mistaken you'd have to investigate FrameworkElement.DataContext property from within the selector to find the context (presets).
Considering your doubt about ConvertBack method, most commonly value or Binding.DoNothing is returned in case you don't need conversion in any of the directions.
I'm new to both Caliburn and WPF, so excuse me if it is a rather trivial question.
The scenario is the following:
I have multiple controls (like buttons and textboxes - the latter is the important part).
Their state (Enabled/Disabled) are dependent on a boolean property.
The first suggested method I tried was using the Can[FunctionName] convention and NotifyOfPropertyChange(() => Can[FunctionName]). It worked well with the button, but it did not work with the textbox.
How do I bind IsEnabled property to a state without using the code-behind of the View?
The code I tried in the ViewModel didn't work for the textbox:
private bool _buttonEnableState = true;
public bool ButtonEnableState
{
get
{
return _buttonEnableState;
}
set
{
_buttonEnableState = value;
NotifyOfPropertyChange(() => CanTheButton);
NotifyOfPropertyChange(() => CanTheTextBox);
}
}
public bool CanTheButton
{
get
{
return ButtonEnableState;
}
}
public void TheButton()
{
}
public bool CanTheTextBox
{
get
{
return ButtonEnableState;
}
}
From the View:
<Button x:Name="TheButton" Content="This is the button" ... />
<TextBox x:Name="TheTextBox" ... />
Thanks in advance!
Have you tried the obvious?:
<Button Content="This is the button" IsEnabled="{Binding ButtonEnableState}" />
<TextBox x:Name="TheTextBox" IsEnabled="{Binding ButtonEnableState}" />
UPDATE >>>
So, continuing the conversation from the comments... now you have a public property in your AppViewModel class and an instance of that class is set as the DataContext of your view that contains the Button and TextBox controls?
Let's see if the Binding is really working or not... try changing your code to this:
<Button Content="{Binding ButtonEnableState}" />
If the Button.Content is set then the Binding works just fine and you have a different problem.
UPDATE 2 >>>
As #Charleh mentioned, you also need to make sure that you have notified the INotifyPropertyChanged interface of the change of property value:
NotifyOfPropertyChange(() => ButtonEnableState);
I don't think what I'm about to suggest is necessarily the correct way of doing things, but it may give you the result you're after.
In order to get the control to be disabled based on a Can<name> property, you need to confirm to the conventions that Caliburn uses, so in this case, supplying a function <name> should work:
public void TheTextBox()
{
}
As a result of the default conventions, I believe this will be called every time the KeyDown event is fired.
That said, you probably want to bind your text content to something, and you'll want to use the x:Name property convention to choose which property, that means you'll have to attach the TheTextBox() function in a different way, you should be able to do that using the Message.Attach property in the Caliburn namespace.
So your TextBox could look like this (where you've added the following namespace xmlns:cal="http://www.caliburnproject.org"):
<TextBox cal:Message.Attach="TheTextBox" Name="SomeTextProperty" />
Backing that up in your ViewModel, you'd have:
// Your Enabled Property (using your existing code).
public bool CanTheTextBox
{
get
{
return ButtonEnableState;
}
}
// Your dummy function
public void TheTextBox()
{
}
// Some text property (Just for demo, you'd probably want to have more complex logic in the get/set
public string SomeTextProperty
{
get; set;
}
You should then see the Enabled/Disabled behaviour, and be use the SomeTextProperty.
I'm not entirely sure I like this way of doing things, I just had a quick play to see if it worked. The following answer might be a cleaner solution, and establishes a new re-usable convention:
Adding a convention for IsEnabled to Caliburn.Micro
As a slight aside (not a direct answer), depending on how complicated your control/form is, you could investigate using multiple Views for the same ViewModel, in the past I've set up a ReadOnly and Editable view, and used a single property on the ViewModel to toggle between the two (essentially setting the entire state of the ViewModel). There are already default conventions so you can use multiple views with relative ease.
I'm curious how this works, because I have a MainViewModel, which has Property say called SubViewModel which has a Property of ObservableCollection (we'll call it Property1.)
I've implemented INotifyChangedProperty on everything.
My Main Window
<Window ..
DataContext="{Binding MainViewModel}" />
...
<StackPanel DataContext="{Binding SubViewModel}">
<local:SomeControl DataContext="{Binding}" />
</StackPanel>
</Window>
And my UserControl
<UserControl Name="SomeControl">
<DataGrid Name="MyDataGrid" ItemSource="{Binding Property1, Mode=TwoWay}" CurrentCellChanged="TestMethod" />
...
</UserControl>
In my test method, just as a test to figure out why the changes are not propegating up to the main view model I do something like this
private void TestMethod()
{
var vm = this.DataContext as SubViewModel;
var itemSourceObservableCollection = MyDataGrid.ItemsSource as ObservableCollection<MyType>;
//I thought vm.Property1 would be equal to itemSourceObservableCollection
//but they are not, itemSourceObservableCollection shows the changes I've made
//vm.Property1 has not reflected any changes made, even though I though they were the same item
}
So I figured out that ItemSource must create a copy of the item you bind it to? I'm stuck here, how do manually notify the viewModel that this property has changed and it needs to update? I thought that was INotifyPropertyChanged's job?
I think part of my problem is I lack the understanding of how this kinda works internally. If anyone can point to a good blog post, or documentation to help me understand why my code isn't working the way I expected, that would be great.
1) No copy is made.
2) ObservableCollection will propogate changes made to the collection, not the items within the collection. So you'll see additions, deletions etc. but NOT property changes to items within the collection.
3) If you want to see changes made to individual items in the ObservableCollection, you need to implement INotifyPropertyChanged on those items.
There's actually TWO different issues here. What happens internally when you bind to a collection? AND why changes on the user surface are not propagated back to your View Model. Based upon what you wrote, the two issues are not connected, but let's take them one at a time...
For the first issue... When you bind a collection, the WPF binding engine creates a "CollectionView" class that mediates between your object store and the logical tree. You can, if needed, get a copy of the the "CollectionView" using a static method on CollectionViewSource...
var cvs = CollectionViewSource.GetDefaultView(MyCollectionOfThings);
There are several interesting properties in the result, and some of them contain write accessors which allow you to directory modify the CollectionView.
For the second issue... The business classes in your SubViewModel need to inherit from INotifyPropertyChanged such that changes are 'announced' via the WPF binding engine. Your VM should be a publisher, but can also be a subscriber. A property that participates in the INotifyPropertyChanged plumbing gets declared like this...
private string _name;
[Description("Name of the driver")]
public string Name
{
[DebuggerStepThrough]
get { return _name; }
[DebuggerStepThrough]
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
This code publishes changes, but can also subscribe to changes made on the user surface by setting the appropriate attributes in your Xaml.
Background reading: What is a CollectionView?
Also, Similar question
I'm using a Silverlight 4.0 project that utilizes MVVM and we have a combobox that resides on a view and has its values and selected value bound to an observable collection of Organizations and a SelectedOrganization respectively (both values exist on the viewmodel). With our project the page that this control is on can be hidden or shown. The first load everything looks great but when you go to a different control (hide the tab with the control and then go back to it) the value that is currently selected in the combo box looks like it's blank, but when I debug, the selected value is still there.
The visual tree is getting recreated, but I have no idea why the combobox loses the text that should be in the box when the parent page is hidden and then re-shown. All other controls on the page behave correctly (autocompletetextbox, textblocks, textboxes, all of which have data bound to the viewmodel the same way).
Here's how the combobox is declared:
<ComboBox
SelectedItem="{Binding SelectedOrganization, Mode=TwoWay}"
ItemsSource="{Binding Organizations}"
DisplayMemberPath="Name"
Margin="5,0"
MinWidth="100" />
the Class for the organization is here:
[DataContract]
public class Organization
{
[DataMember]
public Guid OrganizationID { get; set; }
[DataMember]
public string Name { get; set; }
}
and the viewmodel has the following code for the bindings:
public Organization SelectedOrganization
{
get { return (Organization)GetValue("SelectedOrganization"); }
set
{
SetValue("SelectedOrganization", value);
}
}
public ObservableCollection<Organization> Organizations
{
get { return (ObservableCollection<Organization>)GetValue("Organizations"); }
set { SetValue("Organizations", value); }
}
What do I need to do to keep the selected value when I switch parent pages?
The problem is that I declared the ItemsSource AFTER the SelectedItem. Apparently this is a bug in Silverlight 3 and 4. The answer was discussed here Silverlight Combobox and SelectedItem.
Just a quick note on yet a SL3 bug.
I haven't reproduced this bug in a clean environment (since I'm getting quite tiered of reproducing SL ComboBox bug's...), but I experienced an issue with roughly this setup:
ItemsSource binds to property of type List on Object X.
SelectedItem binds to property of type String on Object X.
Object X implements INotifyPropertyChanged.
SelectedItem is set after ItemsSource in XAML code as the above post instructs.
ItemsSource is set to TwoWay BindingMode.
Behaviour: When the user TAB's from a textbox into the combobox, the combobox value is 'blanked', while the ViewModel maintains its value. The value is displayed again correctly when the user TAB's out of the ComboBox. Please note that the value is not blanked, if the combobox is merely clicked, or if it tabbed to from another combobox.
Solution: When stepping through the code with a debug'er it seems that SelectedItem is returned before ItemsSource, even though ItemsSource is declared before SelectedItem in the XAML code.
The solution was to change the ItemsSource from TwoWay BindingMode to OneWay BindingMode.
Probably this prevents some events from being fiered behind the scenes.
Br. Morten
Could be helpful if adding to previous post, I noticed that my selectedItem binding property must contain a conditional that avoid assing null value, because combobox control still wants to reset value once the control is hidden by scroll or whatever.
i.e. :
public string Month{
get {return _month;}
set {
if (value==null)
return;
_month = value;
}
}
Use this
<ScrollViewer Grid.Row="6" Grid.ColumnSpan="4" Height="190">
<sdk:DataGrid Name="datagridInvestigation"
AutoGenerateColumns="False" Width="650"
MinHeight="180" >
</sdk:DataGrid>
</ScrollViewer>
instead of
<sdk:DataGrid Name="datagridInvestigation"
AutoGenerateColumns="False" Width="650"
Height="180" ScrollViewer.HorizontalScrollBarVisibility="Auto" >
I have a databound TextBlock control (which is being used inside a DataTemplate to display items in a ListBox) and I want to make all the text in the control bold. I can't seem to find a property in the properties explorer to set the whole text to bold, and all I can find online is the use of the <Bold> tag inside the TextBlock, but I can't put that in as the data is coming directly from the data source.
There must be a way to do this - but how? I'm very inexperienced in WPF so I don't really know where to look.
Am I missing something, or do you just need to set the FontWeight property to "Bold"?
<TextBlock FontWeight="Bold" Text="{Binding Foo}" />
Rather than just having a TextBlock, try this:
<TextBlock>
<Bold>
<Run />
</Bold>
</TextBlock>
Then databind to the Run.TextProperty instead.
You say that the data is coming directly from the datasource; is it possible to place a layer of abstraction in front of it? Its quite common to create a View for what you are displaying, and have the View communicate with the data. The most common implementation of this idea is Model View View-Model (MVVM). Have a read about it online.
You might have a 'DisplayText' property that is bound to the textbox, and it is simply a 'getter' that wraps the underlying text. It can detect if the text is already wrapped in and if not, wrap it.
Eg.
public class TestView {
private Test datasource;
public TestView(Test source)
{
this.datasource = source;
}
public string DisplayText {
get {
if (datasource.Text.Contains("<bold>")==false) {
return "<bold>" + datasource.Text + "</bold>";
}
return datasource.Text;
}
}
}
Then, bind to the View instead of directly to the object.