I'm new to ReactiveUI. I'm trying to combine PRISM.MVVM with ReactiveUI. In particular, I'd like to use PRISM for its navigation utilities and ReactiveUI for creating the UI.
I'm not sure about using code in view code-behind even if I use the MVVM pattern: is there a way to bind the UI control to the ViewModel property without creating binding in code-behind?
Here is an example:
ViewModel:
public class MainWindowViewModel : ReactiveObject
{
private string _inputValue;
public string InputValue
{
get => _inputValue;
set => this.RaiseAndSetIfChanged(ref _inputValue, value);
}
private string _outputValue;
public string OutputValue
{
get => _outputValue;
set => this.RaiseAndSetIfChanged(ref _outputValue, value);
}
public MainWindowViewModel()
{
this.WhenAnyValue(x => x.InputValue).Subscribe(x => OutputValue = x);
}
}
View:
<rxui:ReactiveWindow
xmlns:rxui="http://reactiveui.net"
x:Class="WPFReactiveUI.Views.MainWindow"
x:TypeArguments="viewModels:MainWindowViewModel"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:WPFReactiveUI.ViewModels"
Title="ReactiveUI and PRISM sample" Height="350" Width="525" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" x:Name="InputText" Text="{Binding InputValue}" />
<Label Grid.Row="0" Grid.Column="1" x:Name="OutputLabel" Content="{Binding OutputValue}"/>
</Grid>
</rxui:ReactiveWindow>
View code-behind:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
ViewModel = new AppViewModel();
this.WhenActivated(disposable =>
{
this.Bind(ViewModel, x => x.Input, x => x.InputText.Text)
.DisposeWith(disposable);
this.Bind(ViewModel, x => x.Output, x => x.OutputLabel.Content)
.DisposeWith(disposable);
});
}
}
This example is working fine, but I don't understand if there is a way not to write code in the code-behind or if I'm obliged to do it because of ReactiveUI.
Thanks in advance
There's a way. Not sure you're going to like it but there is a way.
You can minimise the boiler plate code you have to write.
You have to write some code AND you complicate each property in your viewmodel though.
You can now use code generators which generate code files in the build. These can read classes and code you have, add code to your project.
You could write one of these.
Usually, this is attribute driven. But you can write code which examines your source code and emits more source code to your specification.
You write the code generates the strings.
An example is clearly explained here:
https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/overview
As it says you have
[ObservableProperty]
private string? name;
And the source generator creates the public property Name with setproperty code which raises property changed.
Your generated code goes into a file there on disk to be compiled into your app. You can find and look at it.
You would need convention or another attribute specifies where a viewmodel is used. ( Yep. Bit smelly that ).
You would need a string in your own attribute as a parameter tells it what control name you want to bind to. ( Yep. Also a bit smelly ).
That file would be a partial class for mainwindow with just your generated reactiveui not-binding code in it.
You'd arrange for that to be in a method which takes that disposable parameter and run that as well as any code you write. Roughly:
public MainWindow()
{
InitializeComponent();
ViewModel = new AppViewModel();
this.WhenActivated(disposable =>
{
this.Bind(ViewModel, x => x.Input, x => x.InputText.Text)
.DisposeWith(disposable);
this.Bind(ViewModel, x => x.Output, x => x.OutputLabel.Content)
.DisposeWith(disposable);
GeneratedBindingCodeMethod(disposable);
});
}
Here, GeneratedBindingCodeMethod is in a partial mainwindow class which is generated by your source generator.
This cannot generate all your binding code. Or hopefully not anyhow. If there is no translation then you rather wasted your time with all those lambdas
Or... you know.. you could maybe just use viewmodel first "navigation" and ditch prism. Just use someone else's code generator and go with the community toolkit.
Personally, I prefer that toolkit to reactiveui and or prism.
Related
I'm just getting started with the toolkit and I'm trying to generate a simple ObservableProperty to use with WPF. I create a usercontrol:
<UserControl x:Class="WPF_test.StatusControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPF_test"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBox x:Name="txtTest" Text="{Binding testData}" Grid.Column="0" Grid.Row="0" Margin="5,5,5,5" />
</Grid>
</UserControl>
and a ViewModel:
using System;
using CommunityToolkit.Mvvm;
using CommunityToolkit.Mvvm.ComponentModel;
namespace WPF_test
{
[ObservableObject]
public partial class StatusControlViewModel
{
[ObservableProperty]
private String? testData;
}
}
I embed the control into the MainWindow and set the datacontext in codebehind:
public partial class MainWindow : Window
{
StatusControlViewModel model;
public MainWindow()
{
InitializeComponent();
model = new StatusControlViewModel();
status.DataContext = model;
model.testData = "test";
}
}
but I see that model.testData is inaccessible due to its protection level. When I comment this line out in order to get the code to run I get a binding error saying that testData cannot be found.
This is the generated code:
namespace WPF_test
{
partial class StatusControlViewModel
{
/// <inheritdoc cref="testData"/>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.0.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public string? TestData
{
get => testData;
set
{
if (!global::System.Collections.Generic.EqualityComparer<string?>.Default.Equals(testData, value))
{
OnTestDataChanging(value);
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.TestData);
testData = value;
OnTestDataChanged(value);
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.TestData);
}
}
}
/// <summary>Executes the logic for when <see cref="TestData"/> is changing.</summary>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.0.0.0")]
partial void OnTestDataChanging(string? value);
/// <summary>Executes the logic for when <see cref="TestData"/> just changed.</summary>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.0.0.0")]
partial void OnTestDataChanged(string? value);
}
}
It seems that the toolkit is capitalising my property name. I can make the databinding work by capitalising the property name in the control XAML:
<TextBox x:Name="txtTest" Text="{Binding TestData}" Grid.Column="0" Grid.Row="0" Margin="5,5,5,5" />
and similarly access the model property:
model.TestData = "test";
Is there a way to use the toolkit so that the property is accessed in the original form, i.e.
<TextBox x:Name="txtTest" Text="{Binding testData}" Grid.Column="0" Grid.Row="0" Margin="5,5,5,5" />
not
<TextBox x:Name="txtTest" Text="{Binding TestData}" Grid.Column="0" Grid.Row="0" Margin="5,5,5,5" />
? I think it's going to be confusing otherwise.
No.
There is no way "to use the toolkit so that the property is accessed in the original form"
Because that's a field and not a property.
The code generator is literally generating some code.
The property name cannot be the same as your backing field. testData is a field. You cannot bind to a field.
[ObservableProperty]
private String? testData;
Generates a TestData property in the partial class.
public string? TestData
{
get => testData;
set
{
if (!global::System.Collections.Generic.EqualityComparer<string?>.Default.Equals(testData, value))
{
OnTestDataChanging(value);
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.TestData);
testData = value;
OnTestDataChanged(value);
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.TestData);
}
}
}
You can only bind to public properties so you need what that generates in order to bind. No property means no binding.
Binding:
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/?view=netdesktop-6.0
Property:
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/properties
In the above _seconds is a field.
Hours is a property.
The difference is properties have getters and (optional) setters.
Having said all that.
You are not forced to use that attribute.
You can create your properties manually.
Then you can have whatever case you like for your property.
I suggest you learn to like upper case properties though.
I've worked for a lot of clients. The standard for property names has always been to start with an upper case letter.
PS
[Relaycommand] generates an upper class property with "Command" appended.
So this is my first MVVM application. I have a "shell" view model named MainWindowViewModel for the main window that basically splits the view into two pages: MainWindowRibbon and MainWindowFrame. The MainWindowViewModel stores both pages as properties, which I plan to use databinding to update in the UI. Here is some of the code for reference:
MainWindowView xaml~
<Grid>
<Frame Content="{Binding MainWindowRibbon}" Grid.Column="0" Grid.Row="0"/>
<ScrollViewer>
<Frame Content="{Binding MainWindowFrame}"/>
</ScrollViewer>
</Grid>
MainWindowView code behind~
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
mainWindowViewModel = new MainWindowViewModel();
DataContext = mainWindowViewModel;
}
public MainWindowViewModel mainWindowViewModel;
}
MainWindowViewModel code~
public MainWindowViewModel()
{
//MainWindowRibbon and MainWindowFrame are declared as public Page properties
MainWindowRibbon = new MainWindowRibbonView();
MainWindowFrame = new WelcomePageView();
}
The MainWindowRibbonView, like the MainWindowView, instantiates the MainWindowRibbonViewModel.
My trouble comes when I wish to use an event within the MainWindowRibbonViewModel that will call for the MainWindowViewModel to reassign the MainWindowFrame page. I do not know how to connect the button command of the navigation bar I have created in the MainWindowRibbonView to cause an event or change in the MainWindowViewModel.
I do not know if the way I have organized this is ideal. Please let me know if I need to revise.
If somebody could help me determine the best approach, or even just a functioning one, I would be very grateful.
P.S.
Sorry if the naming conventions aren't the greatest.
Edit:
Lesson learned: listen to Joe.
I suppose it depends on what kind of button you are using in your navigation bar. is it a RadioButton? A RibbonToggleButton? Is it a regular button binding to an ICommand?
Since you called your Navigation Bar a "Ribbon", let us suppose it is a RibbonToggleButton (which is still basically a CheckBox). If it is checked, you show some view-model your "page 1". If it is not checked, you should another view-model representing your "page 2"
Let us also suppose your view's ribbon is up top. So you have two rows: the Ribbon row and the content row.
I might rewrite your MainWindow to look like this, (note the IsChecked property to some boolean property in your view-model like so:)
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- The "Ribbon" -->
<RowDefinition Height="*"/> <!-- The page content -->
</Grid.RowDefinitions>
<ToggleButton Content="Show Page 1" IsChecked="{Binding ShowPage1}"/>
<ScrollViewer Grid.Row=1>
<Frame Content="{Binding CurrentViewModel}"/>
</ScrollViewer>
</Grid>
And I might write your view-model like this: (Note that I assume it implements INotifyPropertyChanged and I call a function RaisePropertyChanged that I do not show.
public class Page1ViewModel {} // Fill this out with Page 1 properties
public class Page2ViewModel {} // Fill this out with Page 2 properties
// MainWindowViewModel. Implements INotifyPropertyChanged. Implementation
// is not shown here.
public class MainWindowViewModel : INotifyPropertyChanged
{
private Page1ViewModel = new Page1ViewModel();
private Page2ViewModel = new Page2ViewModel();
public MainWindowViewModel()
{
_currentViewModel = Page1ViewModel;
ShowPage1 = true;
}
private object _currentViewModel;
// The current contents of the main frame.
public object CurrentViewModel
{
get => _currentViewModel;
set
{
if (value == _currentViewModel)
return;
_currentViewModel = value;
RaisePropertyChanged();
}
// Should CurrentViewModel be page 1 or page 2?
public bool ShowPage1
{
get => return _currentViewModel == Page1ViewModel;
set
{
if (value == ShowPage1)
return;
CurrentViewModel = value ? Page1ViewModel : Page2ViewModel;
RaisePropertyChanged();
}
}
}
Note that I am not showing you any of the properties of Page1VieModel or Page2ViewModel nor have I shown you the implicit DataTemplates I assume you will write for them.
Also I am assuming that your navigation bar (and MainWindowView in general) have a DataContext that is already set to the MainWindowViewModel
The implemention with a command button or a RadioButton would be quite different.
I got a generic base-class for ViewModel of my user-control:
public class SuggestModule<TEntity> : ViewModelBase
where TEntity : class, ISuggestable, new()
{
public SuggestModule(ISomeService someService)
{
// Some logic
}
// Some private fields, public properties, commands, etc...
}
}
Whitch has many inheritable classes. That is two of them, for example:
public class CitizenshipSuggestViewModel : SuggestModule<Citizenship>
{
public CitizenshipSuggestViewModel(ISomeService someService)
: base(someService) { }
}
public class PlaceOfBirthSuggestViewModel : SuggestModule<PlaceOfBirth>
{
public PlaceOfBirthSuggestViewModel(ISomeService someService)
: base(someService) { }
}
That is view implementation:
<catel:UserControl
x:Class="WPF.PRC.PBF.Views.UserControls.SuggestUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://schemas.catelproject.com"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:pbf="clr-namespace:WPF.PRC.PBF">
<Grid>
<TextBox Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"> />
<ListBox ItemsSource="{Binding ItemsCollection}" />
// Other elements, behaviors, other extensive logic...
</Grid>
</catel:UserControl>
Now, in MainWindow creating two ContentControls:
<catel:Window
x:Class="WPF.PRC.PBF.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://schemas.catelproject.com">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" Content="{Binding CitizenshipSuggestViewModel, Converter={catel:ViewModelToViewConverter}}" />
<ContentControl Grid.Row="1" Content="{Binding PlaceOfBirthSuggestViewModel, Converter={catel:ViewModelToViewConverter}}" />
</Grid>
</catel:Window>
Due to violation of the Naming Convention, manually resolving a ViewModel in App.xaml.cs:
var viewModelLocator = ServiceLocator.Default.ResolveType<IViewModelLocator>();
viewModelLocator.Register(typeof(SuggestUserControl), typeof(CitizenshipSuggestViewModel));
viewModelLocator.Register(typeof(SuggestUserControl), typeof(PlaceOfBirthSuggestViewModel));
var viewLocator = ServiceLocator.Default.ResolveType<IViewLocator>();
viewLocator.Register(typeof(CitizenshipSuggestViewModel), typeof(SuggestUserControl));
viewLocator.Register(typeof(PlaceOfBirthSuggestViewModel), typeof(SuggestUserControl));
But now I have two views with identical ViewModels.
How can I solve this problem without creation of identical Views with repetition of the code in each of them?
Thank you in advance!
You have a few options:
Repeat yourself so if one needs customization in the future, you can customize just 1 without much overhead. The downside is that if you need to check the generic behavior, you'll need to change them all.
Create an enum representing the state that a VM is representing. In that case, you can simply creat a single vm that catches all of the cases that you need to handle. You can solve this using a dependency property on the view and by using ViewToViewModelMapping to automatically map this to the vm. This comes closest to achieving code-reusage as you wish to achieve with your view. It does go a bit against "separation of concerns", but since it represents the same sort of data I believe it's still a good approach.
For 2, you need to do the following:
1 Create an enum SuggestEntityType with PlaceOfBirth, Citizenship, etc
2 Create a property on the vm (this example code is assuming you are using Catel.Fody):
public SuggestedEntityType EntityType { get; set; }
3 Create a dependency property on the view:
[ViewToViewModel(MappingType = ViewToViewModelMappingType.ViewToViewModel)]
public SuggestedEntityType EntityType
{
get { return (SuggestedEntityType) GetValue(EntityTypeProperty); }
set { SetValue(EntityTypeProperty, value); }
}
public static readonly DependencyProperty EntityTypeProperty = DependencyProperty.Register("EntityType", typeof (SuggestedEntityType),
typeof (MyControl), new PropertyMetadata(null));
4 You can now use the user control like this:
<controls:MyView EntityType="Citizenship" />
For more info, see http://docs.catelproject.com/vnext/catel-mvvm/view-models/mapping-properties-from-view-to-view-model/
One possibility is to create a dependencyProperty in your UserControl codebehind, for example
#region Properties
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
#endregion Properties
#region Dependency Properties
public static readonly System.Windows.DependencyProperty TestProperty =
System.Windows.DependencyProperty.Register("Test", typeof(string), typeof(YourUserControl), new System.Windows.FrameworkPropertyMetadata() { BindsTwoWayByDefault = true });
#endregion Dependency Properties
Then in your xaml you can bind this property as :
<TextBlock Text="{Binding Test, RelativeSource={RelativeSource AncestorType={x:Type catel:UserControl}, Mode=FindAncestor}}">
Then in your MainWindow you can write:
<views:YourUserControlName Test="{Binding SomeTextPropertyFromMainWindowVM}"/>
So you will be able to bind from the property SomeTextPropertyFromMainWindowVM in your windowVM to some property in your userControl.
If you have in your main window several viewModels, you can write like:
<views:YourUserControlName Test="{Binding SomeViewModel.SomeTextProperty}"/>
<views:YourUserControlName Test="{Binding SomeOtherViewModel.SomeTextProperty}"/>
I am trying to use Data binding to bind an ObservableCollection to the ItemsSource of a DataGrid, as I learn about WPF and stuff.
In the code-behind I can set the DataContext with this.DataContext = this; or bloopDataGrid.DataContext = this;. That's fine and dandy.
I thought I could try something like
<Window.DataContext>
<local:MainWindow/>
</Window.DataContext>
in my main window, but this causes a Stack Overflow Exception as explained in this question. Fine, that makes some sense.
After reading this and other questions/answers that say to try DataContext="{Binding RelativeSource={RelativeSource Self}}" in the window's XAML code, I thought I could actually do this. Apparently I cannot. Or at least, the IDE lets me and it's syntactically correct, but does not do what I want (ie, exactly what this.DataContext = this; does).
Then I read this about using "{Binding ElementName=, Path=}" and tried to use it like so:
<DataGrid
Name="bloopDataGrid"
Grid.Row="1"
ItemsSource="{Binding ElementName=testWin, Path=OutputCollection}">
</DataGrid>
Which also doesn't work. Maybe not for the same reason, but I can't figure out the problem with it.
Oddly, I can't replicate the rebinding example shown in Rachel Lim's blog post.
XAML:
<Window
x:Class="DataBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525"
x:Name="testWin">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="{Binding text}">
</Label>
<DataGrid
Name="bloopDataGrid"
Grid.Row="1"
ItemsSource="{Binding Path=OutputCollection}">
</DataGrid>
</Grid>
</Window>
C#:
using System;
using System.Collections.ObjectModel; //For ObservableCollection<T>
using System.Windows;
namespace DataBinding
{
public partial class MainWindow : Window
{
public String text { get; set; }
public ObservableCollection<testStruct> OutputCollection { get; set; }
public struct testStruct
{
public testStruct(String x, String y) : this()
{
Col1 = x;
Col2 = y;
}
public String Col1 { get; set; }
public String Col2 { get; set; }
}
public MainWindow()
{
InitializeComponent();
testA t1 = new testA();
this.DataContext = this;
//this.DataContext = t1;
//bloopDataGrid.DataContext = this;
text = "bound \"this\"";
t1.text = "bound a class";
OutputCollection = new ObservableCollection<testStruct>();
OutputCollection.Add(new testStruct("1", "2"));
OutputCollection.Add(new testStruct("3", "4"));
}
public class testA
{
public String text { get; set; }
}
}
}
The above code is what I'm using to test this, and is currently using the code-behind version which correctly gives me
What am I doing wrong, which is preventing me from getting the same results as the above picture but by using XAML for the DataContext handling? Am I not connecting the dots properly? ...am I missing some dots?
<Window.DataContext>
<local:MainWindow/>
</Window.DataContext>
is not the same as
this.DataContext = this;
The first one is creating a new instance of the MainWindow class and assigning that to the DataContext property of the Window, while the second is assigning the very same instance of the Window to its DataContext property.
In order to achieve that in XAML, you need to use a RelativeSource Binding:
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}">
</Window>
Edit:
The difference in behavior between defining the DataContext in XAML and in code behind is caused by the fact that the XAML is actually parsed when the constructor finishes executing, because the Dispatcher waits for the user code (in the constructor of the Window) to finish before executing its pending operations.
This causes the actual property values to be different in these different moments, and since there is no INotifyPropertyChanged, WPF has no way of updating the UI to reflect the new values.
You could implement INotifyPropertyChanged in the Window itself, but I suggest creating a ViewModel for this, as I don't like the fact of mixing INotifyPropertyChanged (which is more of a ViewModel concept) with DependencyObject-derived classes (UI elements).
Im all new in the world of C# and .net platform,so please be easy on me.
This forum helped me in a few problems that i came into while doing my project,but im now stuck on this for a few days.
What i'm trying to achieve is to set the selecteditem of a combobox by passing a string to it.
The scenario is :
I have a datatable and im setting the combo's itemssource to that datatable.DefaultView.
Also i set the DisplayMemberPath of the combo,and so far everything is ok,the items show up in the combobox.
Beside this i have a string with some value that i have inside the combobox too.
So i'm trying to set the selecteditem of the combo like this :
combo.SelectedItem = mystring;
As you can guess,it's not working. Strangely,when i do this:
combo.Items.Add(mystring);
combo.SelectedItem = mystring;
It's working. So this is why I'm confused!
EDIT:
I just found the solution :
combo.ItemsSource = datatable.DefaultView;
combo.DisplayMemberPath = "yourpath";
combo.SelectedValuePath = "yourpath";
combo.SelectedValue = mystring;
So the trick was to set the SelectedValuePath and the SelectedValue properties.
I don't know is this a good programming practice,but this does exactly what i needed.
You're doing something wrong.
Here's a demo app that shows this (the project should be named "StringCombo").
<Window
x:Class="StringCombo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
ResizeMode="CanResize">
<Window.DataContext>
<ViewModel
xmlns="clr-namespace:StringCombo" />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ComboBox
Name="OldeFashonedCombo" />
<Button
Grid.Column="1"
Content="Select Olde Waye"
Click="Button_Click" />
<ComboBox
Grid.Row="1"
ItemsSource="{Binding Strings}"
SelectedItem="{Binding SelectedString}" />
<Button
Grid.Row="1"
Grid.Column="1"
Content="Select New Way"
Command="{Binding SelectString}" />
</Grid>
</Window>
We've got two combos and two buttons. One uses the old winforms method of codebehind to manipulate the combo, and the other uses the new MVVM pattern.
In both scenarios, the user clicks the button, it sets the combo's SelectedValue, and the combo updates on the ui.
Here's the codebehind version:
public MainWindow()
{
InitializeComponent();
OldeFashonedCombo.Items.Add("One");
OldeFashonedCombo.Items.Add("Two");
OldeFashonedCombo.Items.Add("Three");
}
private void Button_Click(object sender, RoutedEventArgs e)
{
OldeFashonedCombo.SelectedItem = "Two";
}
Notice I'm not using the same "instance" of "Two"; there is no need as strings are "interned," or the same instance is automatically reused, in the .NET platform. object.ReferenceEquals("Two","Two") is always true.
So, I add strings to the Items collection, and when the button is clicked I set the SelectedItem to "Two". SelectedItem is the actual instance within the Items collection that should be selected. SelectedValue is the display value; you can select by this IIRC, but I wouldn't do that as a best practice.
Here's the MVVM version:
public sealed class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> Strings { get; private set; }
public ICommand SelectString { get; private set; }
public string SelectedString { get; set; }
public ViewModel()
{
Strings = new ObservableCollection<string>();
Strings.Add("Foo");
Strings.Add("Bar");
Strings.Add("Baz");
SelectString = new SelectStringCommand
{
ExecuteCalled = SelectBar
};
}
private void SelectBar()
{
SelectedString = "Bar";
// bad practice in general, but this is just an example
PropertyChanged(this, new PropertyChangedEventArgs("SelectedString"));
}
public event PropertyChangedEventHandler PropertyChanged;
}
/// <summary>
/// ICommands connect the UI to the view model via the commanding pattern
/// </summary>
public sealed class SelectStringCommand : ICommand
{
public Action ExecuteCalled { get; set; }
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
ExecuteCalled();
}
}
Again, because of interning, I do not have to use the same "instance" of the string. To see how the ViewModel connects to the UI, check the bindings on the ComboBox and the Button (If you haven't looked into it yet, I'd strongly suggest ditching codebehind for MVVM. It may take a little more effort to figure it out, but its MUCH better in the long run).
ANYHOW, if you run this app you'd see that BOTH versions work as expected. When you click the button, the combo box is updated properly. This suggests that your code is wrong in some other way. Not sure what, as you haven't given us enough detail to determine this. But if you run the sample and compare it closely with your code, you might be able to figure this out.
I think using the findby will work so something like
combo.ClearSelection();
combo.Items.FindByValue(mystring).Selected = true;