For some reason I can't get my textbox/property binding to work "automatically" with Reactive UI.
My DataContext is setup like this:
My ViewModel is setup in the code behind for the MainWindow.xaml.cs:
public MainWindowViewModel ViewModel { get; protected set; }
public MainWindow()
{
ViewModel = new MainWindowViewModel();
}
In my WPF MainWindow.xaml I have the DataContext and the have a textbox element like this:
(Other properties ommitted)
<Window x:Name="Window">
<Grid DataContext="{Binding ViewModel, ElementName=Window}">
<StackPanel x:Name="ContentGrid" Grid.Row="1">
<TextBox Name="Password" Text="{Binding Password, Mode=TwoWay}" />
...
In the MainWindowViewModel I have a property and field like this:
string _PasswordConfirmation;
public string PasswordConfirmation
{
get { return _PasswordConfirmation; }
set { this.RaiseAndSetIfChanged(x => x.PasswordConfirmation, value); }
}
I also have a command that is setup like this:
var canHitOk = this.WhenAny(
x => x.Password,
x => x.PasswordConfirmation,
(pass, confirm) => (pass.Value == confirm.Value && pass.Value.Length > 3));
OkCommand = new ReactiveCommand(canHitOk);
As you can guess, I also have the password confirmation textbox/property setup too, and the command set for a button (The password confirmation TextBox has the same problem). Since the field/property combos are never updated in the ViewModel, the button is never enabled.
I debugged and confirmed that the ViewModel is bound to the XAML, but the text never gets updated in the field/property combos when typing in the TextBoxes.
I looked at the sample WP7 application in the GitHub repo and they manually bind the KeyUp event to set the text in the text in the property/field. If I follow this convention, I have this in my MainWindow.xaml:
Password.KeyUp += (o, e) =>
{
ViewModel.Password = Password.Text;
};
But is that the right way to do it? I thought Reactive UI would handle the binding as I've seen in other MVVM frameworks, unless I'm mistaken. Is there an easier way?
UPDATE
As Paul Betts pointed out in the comments in his answer, you can enable the binding to automatically update by adding this property to the binding:
UpdateSourceTrigger=PropertyChanged
which would make the TextBox element look like this:
<TextBox Name="Password" Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" />
The problem is that WP7 TextBox doesn't Update its binding automatically on KeyUp - this is a problem with every MVVM framework sadly.
Related
I'm writing WPF application with MVVM structure using MVVM Light.
I have class Foo in the Model:
class Foo: ObservableObject
{
private string _propA = String.Empty;
public string PropA
{
get => _propA ;
set
{
if (_propA == value)
{
return;
}
_propA = value;
RaisePropertyChanged("PropA");
}
}
// same for property PropB, PropC, PropD, etc.
}
And I have some collection of Foo objects in the Model:
class FooCollection: ObservableObject
{
private ObservableCollection<Foo> _items = null;
public IEnumerable<Foo> Items
{
get { ... }
set { ... }
}
public string Name { get; set; }
// ...
// and other methods, properties and fields
}
Now I have a ViewModel where this list is populated via some injected provider:
class MainWindowModel: ViewModelBase
{
private FooCollection _fooList;
public FooList
{
get => _fooList;
set
{
_fooList = value;
RaisePropertyChangedEvent(FooList);
}
}
public MainWindowModel(IFooListProvider provider)
{
FooList = provider.GetFooList();
}
}
And the View, with MainWindowModel as data context:
<TextBlock Text={Binding FooList.Name} />
<ItemsControl ItemsSource="{Binding FooList.Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text={Binding PropA} />
<Button Content={Binding PropB} />
<!-- other controls with bindings -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Everything works fine, I can delete and add new items, edit them and etc. All changes in View reflects automatically in ViewModel and Model via bindings and observable objects, and vice versa.
But now I need to add ToggleButton to data template of ItemsControl, which controls visibility of particular item in other part of window. I need IsChecked value in ViewModel, because control in other part of window is Windows Forms control and I can't bind IsChecked directly without ViewModel.
But I don't want to add new property (Visibility, for example) in model classes (Foo, FooCollection), because it is just an interface thing and it doesn't need to be saved or passed somewhere outside ViewModel.
So my question: what is the best way to add new property to Model collection in ViewModel?
I could create new collection of wrappers in ViewModel (some sort of class Wrapper { Foo item, bool Visibility }) and bind it to ItemsControl. But in this case I have to control adding, removing and editing manually and transfer all changes from List<Wrapper> to FooList.Items, so I don't like this solution. Is there any more simple way to achieve this?
Edition to clarify the question. Now I have:
<ItemsControl ItemsSource="{Binding FooList.Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text={Binding PropA} />
<Button Content={Binding PropB} />
<ToggleButton IsChecked={Binding ????????????} />
<!-- other controls with bindings -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have no field in class to bind IsChecked and I don't want to add it to class, because it's only interface thing and not data model field. How can I, for example, create another collection of bools and bind it to this ItemControl alongside with FooList.Items?
The best place to add the property is of course in the Foo class.
Creating another collection of some other type, add an object per Foo object in the current collection to this one, and then bind to some property of this new object seems like a really bad solution compared to simply adding a property to your current class.
Foo is not an "interface thing", or at least it shouldn't be. It is view model that is supposed to contain properties that the view binds to. There is nothing wrong with adding an IsChecked property to it. This certainly sounds like the best solution in your case.
I'm not sure if I understand why you would need to add a property in the model.
Can't you just use the command property or add an EventTrigger to your toggle button?
(See Sega and Arseny answer for both examples Executing a command on Checkbox.Checked or Unchecked )
This way, when you check the toggleButton, there is a method in your viewModel which enable or disable the visibility property of your Winform control.
To change the visibility of your control from a command in your viewModel, you could use the messenger functionnality of MVVM LIGHT
MVVM Light Messenger - Sending and Registering Objects
The ViewModel sends a message to you're Windows Forms and this one handles the visibility of your control.
I want to understand if we can update a XView datacontext from ZViewModel?
I have a XView and on load it is bound to its DataContext is bound to its XViewModel.
In ZViewModel I create a new instance of XViewModel and want to update the XView's datacontext with this new XViewModel.
Can I do this? Any ideas please?
To answer your question: not as far as I know.
Furthermore, it somewhat goes against the principles of MVVM to have a ViewModel "looking up" into a View at all.
If you're trying to radically change some set of data in XViewModel you might look into the Event Bus in MVVM Light. It can let one VM subscribe to a message of a certain type and perform some action when it receives that message.
You could, in effect:
Have XViewModel register for messages of type T
Have ZViewModel pass a message of type T on the EventBus
XViewModel will execute some handler when it receives ZViewModels message
The beauty is that neither VM knows about the other. It's completely decoupled via the bus.
If your view is a UserControl this is pretty easy. You can set a binding for the DataContext to a property in your ViewModel. Clicking the button would essentially reset the UserControl to it's default state.
<Window
//usual Window declarations>
<Grid x:Name="LayoutRoot">
<view:MyView DataContext="{Binding MyViewModelProperty}" />
<Button Command="{Binding CmdChangeViewModel}"
Content="Change VM" />
</Grid>
</Window>
MainViewModel:
private IViewModelBase _MyViewModelProperty;
public IViewModelBase MyViewModelProperty
{
get { return _MyViewModelProperty; }
set { Set("MyViewModelProperty", ref _MyViewModelProperty, value); }
}
public ICommand CmdChangeViewModel
{
get
{
return new RelayCommand(() =>
{
MyViewModelProperty = new MyViewModel();
});
}
}
If your view is another Window then you'll want to use a ViewModelLocator. I use the one in MVVM Light. Using the same MainViewModel above, clicking a button in your main View would change the DataContext of the other window.
Child Window:
<Window
//usual Window declarations
DataContext="{Binding Main.MyViewModelProperty, Source={StaticResource Locator}}" />
I have a customer usercontrol that is a labeled TextBox (Border wrapped about a Label and a TextBox with the TextBox overlapping the label). I am finding few (working) examples on how to get the TextChanged function to work when called from my UserControl.
Just the textbox snippet:
<TextBox
FontSize="{Binding Path=DefaultFontSize}"
Style="{StaticResource WatermarkTextBox}"
Padding="{Binding Path=TextPadding}"
Tag="{Binding Path=TextValue}"
/>
I have tried using RoutedEventHandler like I did with my button's Click event, but it didn't work. How do I get it so when let's say I use on the window it is required:
<MyControl:LabeledTextBox
TextBoxChange="Some_Event"
TextValue="{Binding SomethingOrOther}"
/>
that it will fire off correctly and do the needed function
This question's really unclear. Do you want your user control to support a TextChanged event that gets raised when the text in the TextBox changes? If so, you need to implement it in the code-behind.
First, declare the event:
public event TextChangedEventHandler TextChanged;
Then, add an event handler to the TextBox:
<TextBox TextChanged="TextBox_TextChanged" ... />
and in the code-behind:
private void TextBox_TextChanged(object sender, TextChangedEventArgs args)
{
TextChangedEventHandler h = TextChanged;
if (h != null)
{
h(this, args);
}
}
If you are using MVVM (Or if your TextValue binding is binding to something you can get to and edit) you can put the logic you want executed in the setter.
So, lets say you are binding to a property MyTextBoxValue. Set the binding mode to two way in the XAML, and in the setter put the logic or call to another method.
If you want the code to fire every time you type, set UpdateSourceTrigger=PropertyChanged in XAML, if you want the code to fire only when text entry is "done" set UpdateSourceTrigger=LostFocus.
This is a running question that I have updated to hopefully be a little more clear.
In short what I am trying to accomplish is pass a property from a listbox selected item to the viewmodel so that this property can be used within a new query. In the code below the Listbox inherits databinding from the parent object. The listbox contains data templates (user controls) used to render out detailed results.
The issue I am having is that within the user control I have an expander which when clicked calls a command from the ViewModel. From what I can see the Listbox object is loosing it's data context so in order for the command to be called when the expander is expanded I have to explicitly set the datacontext of the expander. Doing this seems to instantiate a new view model which resets my bound property (SelectedItemsID) to null.
Is there a way to pass the selected item from the view to the viewmodel and prevent the value from being reset to null when a button calls a command from within the templated listbox item?
I realize that both Prism and MVVMLite have workarounds for this but I am not familiar with either framework so I don't know the level of complexity in cutting either of these into my project.
Can this be accomplished outside of Prism or MVVMLite?
original post follows:
Within my project I have a listbox usercontrol which contains a custom data template.
<ListBox x:Name="ResultListBox"
HorizontalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding SearchResults[0].Results,
Mode=TwoWay}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionChanged="ResultListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<dts:TypeTemplateSelector Content="{Binding}" HorizontalContentAlignment="Stretch">
<!-- CFS Template -->
<dts:TypeTemplateSelector.CFSTemplate>
<DataTemplate>
<qr:srchCFS />
</DataTemplate>
</dts:TypeTemplateSelector.CFSTemplate>
<!-- Person Template -->
<dts:TypeTemplateSelector.PersonTemplate>
<DataTemplate>
<qr:srchPerson />
</DataTemplate>
</dts:TypeTemplateSelector.PersonTemplate>
<!-- removed for brevity -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
SelectionChanged calls the following method from the code behind
private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedItem != null)
_ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
this.NotifyPropertyChanged(_ViewModel.SelectedItemID);//binds to VM
}
Within the ViewModel I have the following property
public string SelectedItemID
{
get
{
return this._SelectedItemID;
}
set
{
if (this._SelectedItemID == value)
return;
this._SelectedItemID = value;
}
}
the listbox template contains a custom layout with an expander control. The expander control is used to display more details related to the selected item. These details (collection) are created by making a new call to my proxy. To do this with an expander control I used the Expressions InvokeCommandAction
<toolkit:Expander Height="auto"
Margin="0,0,-2,0"
Foreground="#FFFFC21C"
Header="View Details"
IsExpanded="False"
DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
Style="{StaticResource DetailExpander}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Expanded">
<i:InvokeCommandAction Command="{Binding GetCfsResultCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Within the ViewModel the delegate command GetCFSResultCommandExecute which is called is fairly straight forward
private void GetCfsResultCommandExecute(object parameter)
{
long IdResult;
if (long.TryParse(SelectedItemID, out IdResult))
{
this.CallForServiceResults = this._DataModel.GetCFSResults(IdResult);}
The issue I am experiencing is when selecting a listbox Item the selectionchanged event fires and the property SelectedItemID is updated with the correct id from the selected item. When I click on the expander the Command is fired but the property SelectedItemID is set to null. I have traced this with Silverlight-Spy and the events are consistent with what you would expect when the expander is clicked the listbox item loses focus, the expander (toggle) gets focus and there is a LeftMouseDownEvent but I cannot see anything happening that explains why the property is being set to null. I added the same code used in the selection changed event to a LostFocus event on the listboxt item and still received the same result.
I'd appreciate any help with understanding why the public property SelectedItemID is being set to null when the expander button which is part of the listbox control is being set to null. And of course I would REALLY appreciate any help in learning how prevent the property from being set to null and retaining the bound ID.
Update
I have attempted to remove the datacontext reference from the Expander as this was suggested to be the issue. From what I have since this is a data template item it "steps" out of the visual tree and looses reference to the datacontext of the control which is inherited from the parent object. If I attempt to set the datacontext in code for the control all bindings to properties are lost.
My next attempt was to set the datacontext for the expander control within the constructor as
private SearchViewModel _ViewModel;
public srchCFS()
{
InitializeComponent();
this.cfsExpander.DataContext = this._ViewModel;
}
This approach does not seem to work as InvokeCommandAction is never fired. This command only seems to trigger if data context is set on the expander.
thanks in advance
With this line you create a new SearchViewModelDataSource using its default constructor.
DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
I guess this is why you find null because this is the default value for reference type.
You can resolve the issue by setting DataContext to the same instance used to the main controll (you can do it by code after all components are initialized).
Hope this help!
Edit
I don't think that binding may be lost after setting datacontext from code. I do it every time I need to share something between two or more model.
In relation to the code you've written :
private SearchViewModel _ViewModel;
public srchCFS()
{
InitializeComponent();
this.cfsExpander.DataContext = this._ViewModel;
}
Instead of using this.cfsExpander you can try to use the FindName method. Maybe this will return you the correct instance.
object item = this.FindName("expander_name");
if ((item!=null)&&(item is Expander))
{
Expander exp = item as Expander;
exp.DataContext = this._ViewModel;
}
Try if its work for you.
Of course, this._ViewModel has to expose a property of type ICommand named GetCfsResultCommand but I think this has been already done.
While this was a hacky approach I found an intermediate solution to get the listbox item value to the view model. I ended up using the selection changed event and passing the value directly to a public property wihtin my view model. Not the best approach but it resolved the issue short term
private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedItem != null)
_ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
MySelectedValue = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
this.NotifyPropertyChanged(_ViewModel.SelectedItemID);
}
For this to fire I did have to also setup a property changed handler within the view to push the change to the VM. You can disregard the MySelectedValue line as it is secondary code I have in place for testing.
For those intereted the generic property changed handler
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
I have a datagrid that is multi-select enabled. I need to change the selection in the viewmodel. However, the SelectedItems property is read only and can't be directly bound to a property in the viewmodel. So how do I signal to the view that the selection has changed?
Andy is correct. DataGridRow.IsSelected is a Dependency Property that can be databound to control selection from the ViewModel. The following sample code demonstrates this:
<Window x:Class="DataGridMultiSelectSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tk="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
Title="Window1" Height="300" Width="300">
<StackPanel>
<tk:DataGrid AutoGenerateColumns="False" ItemsSource="{Binding}" EnableRowVirtualization="False">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="Value" Binding="{Binding Value}" />
</tk:DataGrid.Columns>
<tk:DataGrid.RowStyle>
<Style TargetType="tk:DataGridRow">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</tk:DataGrid.RowStyle>
</tk:DataGrid>
<Button Content="Select Even" Click="Even_Click" />
<Button Content="Select Odd" Click="Odd_Click" />
</StackPanel>
</Window>
using System.ComponentModel;
using System.Windows;
namespace DataGridMultiSelectSample
{
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = new[]
{
new MyViewModel {Value = "Able"},
new MyViewModel {Value = "Baker"},
new MyViewModel {Value = "Charlie"},
new MyViewModel {Value = "Dog"},
new MyViewModel {Value = "Fox"},
};
}
private void Even_Click(object sender, RoutedEventArgs e)
{
var array = (MyViewModel[]) DataContext;
for (int i = 0; i < array.Length; ++i)
array[i].IsSelected = i%2 == 0;
}
private void Odd_Click(object sender, RoutedEventArgs e)
{
var array = (MyViewModel[])DataContext;
for (int i = 0; i < array.Length; ++i)
array[i].IsSelected = i % 2 == 1;
}
}
public class MyViewModel : INotifyPropertyChanged
{
public string Value { get; set; }
private bool mIsSelected;
public bool IsSelected
{
get { return mIsSelected; }
set
{
if (mIsSelected == value) return;
mIsSelected = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Be sure to set EnableRowVirtualisation="False" on the DataGrid element, else there's a risk that the IsSelected bindings fall out of kilter.
I haven't worked with the DataGrid much, but one technique that works for the ListView is to bind to the IsSelected property of the individual ListViewItem. Just set this to true for each object in your list, and then it will get selected.
Maybe the object that represents a row in the DataGrid also has an IsSelected property, and can be used in this way as well?
Guys, thanks for the help. My problem was solved. I think the problem is pretty common for new WPF developers, so I will restate my problem and as well as the solution in more details here just in case someone else runs into the same kind of problems.
The problem: I have a multi-select enabled datagrid of audio files. The grid has multiple column headers. The user can multi-select several row. When he clicks the Play button, the audio files will be played in the order of one the columns headers (say column A). When playback starts, the multi-select is cleared and only the currently playing file is highlighted. When playback is finished for all files, the multi-selection will be re-displayed. The playback is done in the viewmodel. As you can see, there are two problems here: 1) how to select the currently playing file from the viewmodel, and 2) how to signal to the view from the viewmodel that playback is finished and re-display the multi-selection.
The solution: To solve the first problem, I created a property in the viewmodel that is bound to the view's SelectedIndex property to select the currently playing file. To solve the second problem, I created a boolean property in the view model to indicate playback is finished. In the view's code behind, I subscribed the the boolean property's PropertyChanged event. In the event handler, the view's SelectedItems property is re-created from the saved multi-selection (the contents of SelectedItems was saved into a list and SelectedItems was cleared when playback started). At first, I had trouble re-creating SelectedItems. It turned out the problem was due to the fact that re-creation was initiated through a second thread. WPF does not allow that. The solution to this is to use the Dispatcher.Invoke() to let the main thread do the work. This may be a very simple problem for experienced developers, but for newbies, it's a small challenge. Anyway, a lot of help from different people.
Just use SelectedItems on any MultiSelector derived class , and use methods Add, Remove, Clear on IList it returns .