Toggling MenuItem visibility to a GridControl's SelectedItem - c#

I have a GirdControl on my WPF that is bound to objects of type NoteFrontEnd. There is a property in NoteFrontEnd named NoteType that I want to use as the source of a visibility binding in a MenuItem.
The user must right-click on one of the NoteFrontEnd objects from the GridControl, and based on its NoteType property, either show or hide the MenuItem with Header="Process Item".
The GridControl and MenuItem are defined in xaml as:
<dxg:GridControl Name="GridCtrl"
ItemsSource="{Binding Path=BaseDashboardDataSource}"
SelectedItems="{Binding SelectedItems, Mode=TwoWay}"
AutoGenerateColumns="None">
<dxg:GridControl.Columns>
...
<dxg:GridColumn x:Name="NoteType" FieldName="NoteType" Header="Type" />
</dxg:GridControl.Columns>
<dxg:GridControl.View>
<dxg:TableView.ContextMenu>
<ContextMenu>
<ContextMenu.ItemsSource>
<CompositeCollection>
...
<!--Menu Item to toggle visibility of-->
<MenuItem Header="Process Item"
Visibility="{Binding ElementName=GridCtrl, Path=SelectedItem, Converter={StaticResource GVItemToVis}}"
Command="{...}">
</MenuItem>
</CompositeCollection>
</ContextMenu.ItemsSource>
</ContextMenu>
</dxg:TableView.ContextMenu>
</dxg:TableView>
</dxg:GridControl.View>
</dxg:GridControl>
My ViewModel is defined as such:
public class NoteViewModel : DashboardViewModelBase
{
...
public ObservableCollection<NoteFrontEnd> BaseDashboardDataSource { get; private set; }
public ObservableCollection<BrokerNoteFrontEnd> SelectedItems
{
get { return _selectedItems; }
set
{
if (_selectedItems == value) return;
_selectedItems = value;
OnPropertyChanged();
}
}
public NoteViewModel(...) {
...
BaseDashboardDataSource = new ObservableCollection<NoteFrontEnd>();
}
}
And NoteFrontEnd as:
public class NoteFrontEnd
{
public string NoteType { get; set; }
}
I am getting the below error though:
Cannot find source for binding with reference 'ElementName=GridCtrl'. BindingExpression:Path=SelectedItems;...
I have tried other bindings like the below but got the same error:
Visibility="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Grid}, Path=PlacementTarget.SelectedItem, Converter={StaticResource GVItemToVis}}"
How can I get this binding to work?

A context menu is a separate window. It is in a different namescope to your gridcontrol.
I suggest you:
Associate the context menu with the row each NoteFrontEnd is in rather than the entire grid.
NoteFrontEnd should be a viewmodel that implements inotifypropertychanged. Even if you have no changes you want to notify about. Always bind to an object implements inpc and avoid the possibility of a memory leak from bindings.
That then means you can get to the datacontext of that row using either the below or a variant:
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"
That means the datacontext is pointed to the note which is templated into the row clicked.
You can then put a command in your NoteFrontEnd does whatever this is supposed to do.
You could also bind visibility of this menuitem to a property that is based on frontend and encapsulates whatever your logic is.
I suggest you (instead) consider using the canexecute part of icommand and return false when the user isn't supposed to click this option. That way the entry will still be there in the context menu but disabled and greyed.

Related

MenuItem - CustomControl with Event Handler for IsSubMenuOpen

In my WPF MVVM application, when the (sub-)menu of a MenuItem opens, I want to populate the (sub-)menu.
E.g. there is a MenuItem "Logs". Only when the submenu opens, I want to search for corresponding log files on disk and display their filenames in the SubMenu afterwards.
Populating submenu dynamically
MainWindow.xaml
<Grid>
<Menu ItemsSource="{Binding MenuItems}">
<Menu.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MyMenuItemViewModel}"
ItemsSource="{Binding Children}">
<local:CustomMenuItemControl/>
</HierarchicalDataTemplate>
</Menu.Resources>
</Menu>
</Grid>
MyMenuItemViewModel.cs
public class MyMenuItemViewModel : ObservableObject
{
public string Text { get; set; }
public ObservableCollection<MyMenuItemViewModel> Children { get; set; }
public MyMenuItemViewModel(string item)
{
Text = item;
Children = new ObservableCollection<MyMenuItemViewModel>();
}
}
My application is significantly larger, for illustrative purposes I have removed most of it.
I work with a ViewModel that contains a Text and an ObservableCollection "Children" for SubMenus.
It is displayed with a CustomControl that only displays the text.
However, I am already failing to get a trigger when the SubMenu is opened.
I've already tried adding event handler to HierarchicalDataTemplate and CustomMenuItemControl and
a DependencyProperty to the control and tried binding events in XAML, but apparently not in the right place.
Where exactly do I need to define the trigger or handler that executes code when the SubMenu is opened?
I'll answer my own question. Whether it's the best way to solve my problem, I don't know, but that's how I went about it:
In the ViewModel I created a property "IsSubMenuOpen". In the setter, I query whether the text matches "Logs". If yes, I fill the list with the log files.
private bool _isSubMenuOpen;
public bool IsSubMenuOpen
{
get => _isSubMenuOpen;
set
{
if(Text == "Logs")
{
Children.Clear();
foreach (var file in Directory.GetFiles(#"C:\MyDir", "*.log"))
{
Children.Add(new MyMenuItemViewModel(Path.GetFileName(file)));
}
}
SetProperty(ref _isSubMenuOpen, value);
}
}
In the MainWindow I have created a setter for the property "IsSubMenuopen", and bound my VM property in the value.
<Grid>
<Menu ItemsSource="{Binding MenuItems}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsSubmenuOpen" Value="{Binding IsSubMenuOpen}"></Setter>
</Style>
</Menu.ItemContainerStyle>
<Menu.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MyMenuItemViewModel}"
ItemsSource="{Binding Children}">
<local:CustomMenuItemControl/>
</HierarchicalDataTemplate>
</Menu.Resources>
</Menu>
</Grid>

WPF ListView Multi-Select MVVM w/ minimal code behind

I am trying to implement in WPF a way for the user to select multiple items in one box and via button click add those selected items to the other box.
I am trying to adhere to MVVM w/ minimal code behind. The solutions I find show the DataContext being manipulated via the View code behind which I am trying to avoid.
I think my issue is I do not know how to toggle the IsSelected from xaml, but not sure.
XAML
<ListView
ItemsSource="{Binding AvailableStates, Mode=TwoWay}"
SelectedItem="{Binding SelectedStates, Mode=TwoWay}"
SelectionMode="Multiple"
DisplayMemberPath="state"
Grid.Row="1"
Margin="5"
Grid.Column="1"
Height="125"
Name="lvAvailableStates"
Grid.RowSpan="6"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.CanContentScroll="True">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Button
Grid.Row="2"
Grid.Column="2"
Margin="10"
Command="{Binding AddSelectedStatesCommand}"
Content=">>" />
ViewModel
private ObservableCollection<SelectableItemWrapper<states_approved>> _selectedStates;
public ObservableCollection<SelectableItemWrapper<states_approved>> SelectedStates
{
get { return _selectedStates; }
set
{
_selectedStates = value;
OnPropertyChanged();
}
}
private void AddSelectedStates(object obj)
{
var selected = SelectedStates.Where(s => s.IsSelected)
.Select(s => s.Item)
.ToList();
StatesApproved = selected;
}
public CustomCommand AddSelectedStatesCommand
{
get
{
return new CustomCommand(AddSelectedStates, CanExecute);
}
}
Selected Item Wrapper
public class SelectableItemWrapper<T>
{
public bool IsSelected { get; set; }
public T Item { get; set; }
}
ListView has internal property to determine which item is selected and it also has SelectedItems to determine multiple selected items. However, this plural SelectedItems of ListView is not bindable. So, the solution is to pass them as a CommandParameter.
<ListView x:Name="lvAvailableStates"
ItemsSource="{Binding AvailableStates, Mode=TwoWay}"
SelectedItem="{Binding SelectedStates, Mode=TwoWay}" => remove this!
...
<Button Command="{Binding AddSelectedStatesCommand}"
CommandParameter="{Binding SelectedItems, Mode=OneWay, ElementName=lvAvailableStates}" => add this!
...
In the VM
private void AddSelectedStates(IEnumerable<SelectableItemWrapper<states_approved>> selectedItems)
{
StatesApproved = selectedItems
.Select(s => s.Item) // only retrieve the Item
.ToList();
}
As you can see at this point, you don't even really need the SelectableItemWrapper to set/unset the IsSelected property to begin with. You should just remove the wrapper and life will be easier.

TextBox.LineCount and TextBox.GetLastVisibleLineIndex() is always -1 when using MVVM pattern on WPF

I have a TabControl in my View, and I dynamically add TabItems which contains a textbox as a content. And when want to get Line Count from Selected Item, it always returns -1, also with textbox.GetLastVisibleLineIndex(). Code is below:
My View:
<TabControl x:Name="tabControl" HorizontalAlignment="Left" Height="385" Margin="5,50,0,0" VerticalAlignment="Top" Width="740" ItemsSource="{Binding Tabs, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{ Binding SelectedTab, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock
Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<TextBox
Text="{Binding Text, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,diag:PresentationTraceSources.TraceLevel=High }" AcceptsReturn="True" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<cmd:EventToCommand Command="{Binding DataContext.TextChanged, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
My ViewModel:
TabItem tabItem = new TabItem();
tabItem.Header = mainModel.Header;
TextBox textBox = new TextBox();
textBox.Text = mainModel.TextFile;
tabItem.LayoutUpdated += (sender2, e2) => textBox_LayoutUpdated(sender2, e2);
textBox.LayoutUpdated += (sender3, e3) => textBox_LayoutUpdated(sender3, e3);
tabItem.Content = textBox;
Tabs.Add(tabItem);
SelectedTab = tabItem;
private void textBox_LayoutUpdated(object sender, EventArgs args)
{
lineCount = ((SelectedTab as TabItem).Content as TextBox).LineCount;
}
MainModel is my Model in MVVM.
My View.cs:
this.UpdateLayout();
TabItem tab = this.tabControl.SelectedItem as TabItem;
int index = ((this.tabControl.SelectedItem as TabItem).Content as TextBox).GetLastVisibleLineIndex();
Even here in View.cs is always -1;
I am new in WPF MVVM,
Thanks.
You have many issues with your current code. It seems that you actually missed the point of MVVM.
First of all, a view-model must not be aware of the view, nor any view-specific types (e.g. TabItem). A view-model is just a layer that adopts your model in such way that this model can be presented by the view. A view-model must not construct the view itself, as you do it in your example.
The reason why you get -1 is that the TextBox you add into the tab item will never be laid out, because you override the tab item's ContentTemplate.
There are some other things you're either doing wrong or they're unnecessary:
Binding for the tab control's ItemsSource must not be TwoWay, because the tab control itself will never update this property value. The UpdateSourceTrigger is not needed here for the same reason.
UpdateSourceTrigger is not needed for the SelectedItem binding, because it has by default the PropertyChanged mode
you have a command named TextChanged, but by convention it has to be named TextChangedCommand (with a Command postfix)
tabItem.LayoutUpdated += (sender2, e2) => textBox_LayoutUpdated(sender2, e2) is an overkill for an event subscription that creates an unnecessary lambda capturing this, use the method group syntax instead: tabItem.LayoutUpdated += textBox_LayoutUpdated
And now the true MVVM solution:
Suppose you have the item's view-model:
class Item : ViewModelBase
{
public Item(string header, string textFile)
{
Header = header;
this.textFile = textFile;
}
public string Header { get; }
private string textFile;
public string TextFile
{
get => textFile;
set { textFile = value; OnPropertyChanged(); }
}
private int lineCount;
public int LineCount
{
get => lineCount;
set { lineCount = value; OnPropertyChanged(); Debug.WriteLine("Line count is now: " + value); }
}
}
This view-model represents a single item that will be displayed as a tab item. But maybe this will be some other control in the future - actually you don't have to bother with that. The view-model has no idea which controls exactly will display the values. The view-model just provides these values in a convenient way.
So, the Header and the TextFile properties contain the model values. The LineCount property will be calculated by the view (more on that see below).
The main view-model for that would look like this:
class ViewModel : ViewModelBase
{
public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>();
private Item selectedItem;
public Item SelectedItem
{
get => selectedItem;
set { selectedItem = value; OnPropertyChanged(); }
}
}
Note that the Items collection property is read-only. That means no one can change the reference to the collection, but the collection itself is not read-only. The SelectedItem reference may be updated however.
And now the important point: the LineCount property of the TextBox will also be updated, e.g. when the TextBox wraps the text and the control is resized. So we can't just calculate the lines count in view-model, we need to do this in the view.
However, the view-model, as we know, must not be aware of the view. What to do? In such cases a good developer prefers a Behavior from the System.Windows.Interactivity namespace.
Let's create a simple behavior that will monitor the LineCount of a TextBox:
class LineCountBehavior : Behavior<TextBox>
{
public int LineCount
{
get { return (int)GetValue(LineCountProperty); }
set { SetValue(LineCountProperty, value); }
}
public static readonly DependencyProperty LineCountProperty =
DependencyProperty.Register("LineCount", typeof(int), typeof(LineCountBehavior), new PropertyMetadata(0));
protected override void OnAttached()
{
AssociatedObject.LayoutUpdated += RefreshLineCount;
AssociatedObject.TextChanged += RefreshLineCount;
}
protected override void OnDetaching()
{
AssociatedObject.LayoutUpdated -= RefreshLineCount;
AssociatedObject.TextChanged -= RefreshLineCount;
}
private void RefreshLineCount(object sender, EventArgs e)
{
LineCount = AssociatedObject.LineCount;
}
}
Now we can attach this behavior to any TextBox and use the behavior's LineCount dependency property as a binding source. Here is a full XAML setup:
<TabControl ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<TabControl.ItemTemplate>
<DataTemplate DataType="local:Item">
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="local:Item">
<TextBox Text="{Binding TextFile, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AcceptsReturn="True" TextWrapping="Wrap">
<i:Interaction.Behaviors>
<local:LineCountBehavior LineCount="{Binding LineCount, Mode=OneWayToSource}"/>
</i:Interaction.Behaviors>
</TextBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
So this is a 'clean' MVVM solution. Hope I could give you some insights.
BTW, I don't know why you need this line count in the view-model...

Listview Binding Does Not Update Programmatic Changes

I've done some research on the topic, and while I've come across some possibilities, nothing has worked for me.
Details:
I'm working on a WPF app using an MVVM design pattern. In the ViewModel, I have a List of Notes, a class with a few properties (among them, Note). I've created a property, SelectedNote on the VM to hold the currently selected note.
In my View, I've bound a ListView control to the list QcNotes. I've bound a TextBox to the SelectedNote property. When I make changes to the TextBox, they are correctly reflected in the appropriate row of the ListView.
Problem:
I've include a RevertChanges command. This is a relatively simple command that undoes changes I've made to the note. It correctly updates the TextBox, and it actually updates the underlying list correctly, but the changes do not update the ListView itself. (Is it necessary to use an ObservableCollection in this circumstance? I've been asked to try and resolve the problem without doing so).
Attempted Fixes
I tried to call NotifyPropertyChanged("SelectedNote") and NotifyPropertyChanged("QcNotes") directly from within the call to RevertChanges, but that hasn't fixed the problem.
Any ideas?
XAML
<Window.DataContext>
<VM:MainProjectViewModel />
</Window.DataContext>
<Grid>
<StackPanel>
<ListView ItemsSource="{Binding QcNotes, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" x:Name="list" SelectedItem="{Binding SelectedNote}">
<ListView.View>
<GridView>
<GridViewColumn Header="Note" DisplayMemberBinding="{Binding Note}" />
</GridView>
</ListView.View>
</ListView>
<TextBox
Height="30"
HorizontalAlignment="Stretch"
Text="{Binding SelectedNote.Note, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="Allow Edits" Command="{Binding ChangeStateToAllowEditsCommand}" />
<Button Content="Save Changes" Command="{Binding EditNoteCommand}" />
<Button Content="Revert Changes" Command="{Binding RevertChangesToNoteCommand}" />
</StackPanel>
</StackPanel>
</Grid>
ViewModel Code
public class MainViewModel : BaseViewModel
{
private QcNote selectedNote;
private string oldNoteForUpdating;
private VMState currentState;
private string noteInput;
private IList<QcNote> qcNotes;
public IList<QcNote> QcNotes
{
get
{
return qcNotes;
}
set
{
qcNotes = value;
NotifyPropertChanged();
}
}
public QcNote SelectedNote
{
get
{
return selectedNote;
}
set
{
selectedNote = value;
oldNoteForUpdating = SelectedNote.Note;
NotifyPropertChanged();
}
}
public VMState CurrentState
{
get
{
return currentState;
}
set
{
currentState = value;
NotifyPropertChanged();
}
}
public ICommand RevertChangesToNoteCommand
{
get
{
return new ActionCommand(o => RevertChangestoNote());
}
}
private void RevertChangestoNote()
{
QcNotes.First(q => q.Id == SelectedNote.Id).Note = oldNoteForUpdating;
SelectedNote.Note = oldNoteForUpdating;
NotifyPropertChanged("SelectedNote");
NotifyPropertChanged("QcNotes");
CurrentState = VMState.View;
}
I'll post an answer to my own question, but don't want to deter other from offering suggestions.
I implemented the INotifyPropertyChanged interface on my Models.QcNote class, and that resolved the issue. Initially, the interface was implemented exclusively on the ViewModel. In that case, NotifyPropertyChanged was only called when the QcNote object itself was changed, not when the properties of the object were changed.

WPF Binding Combox with different List and different SelectedValue

In my UserControl ucStep2 I have DataContext of Step2InfoData object that has several properties along with :
private string rockDensUnit;
public string RockDensity_Unit
{
get { return rockDensUnit; }
set
{
if (rockDensUnit != value)
{
rockDensUnit = value;
Changed("RockDensity_Unit");
}
}
}
In my app I got to bind several combo's with different normally measurement types Like {kg/m3, gm/m3}, {meter, cm} and so on such groups of measures. I mean, multiple combo's to have list of same items. So I preferred to create Class's of such lists that I can use in multiple combos. I created ComboItems.cs which contains all items lists that I will need to populate the drop down.
ComboItems.cs
//**OBJECTS I USE FOR LIST OF IEMS**
// Class for kg, gm
public class KgGmItems
{
public ObservableCollection<string> KgGmList { get; set; }
public KgGmItems()
{
KgGmList = new ObservableCollection<string>();
KgGmList.Add("kg/m3");
KgGmList.Add("gram/cm3");
}
public string ValueSelected { get; set; } // Don't know if this is useful in my case
}
// Class for meter, cm
public class MtCmItems : INotifyPropertyChanged
{
public MtCmItems()
{
Dict = new Dictionary<string, string>
{
{"meter", "meter"},
{"centimeter", "centimeter"}
};
}
//...
}
XML i.e. ucStep2 View
<!-- As the objects KgGmItems doesn't contain in ucStep2.xaml.cs or Step2InfoData (that is bound to this UC) so add reference of those classes -->
<UserControl.Resources>
<ObjectDataProvider x:Key="KgGmObj" ObjectType="{x:Type top:KgGmItems}" />
<ObjectDataProvider x:Key="MtCmObj" ObjectType="{x:Type top:MtCmItems}" />
</UserControl.Resources>
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}" SelectedValue="{Binding Path=RockDensity_Unit, Mode=TwoWay}" SelectedIndex="0"
Background="#FFB7B39D" Grid.Row="5" Height="23" HorizontalAlignment="Left" Margin="401,61,0,0" Name="comboBox6" VerticalAlignment="Top" Width="84" Visibility="Hidden">
</ComboBox>
I want to display ObservableCllection KgGmList items from KgGmItems class and bind the selected value to RockDensity_Unit of class Step2InfoData that is bound to this UserControl.
In the above combo, I am able to display all items in the drop down, also 1st item is selected by default. But the value is not bind to RockDensity_Unit; it's value remains null.
I want this to happen 2-way i.e. when RockDensity_Unit proeprtiy's value is set programmatically, the value should be selected in the drop down. Of course the value should exists in the list.
By default the 1st item should be selected.
UPDATE
Added DependencyProperty in ucStep2.xaml.cs
public static readonly DependencyProperty RockDensityUnitProperty =
DependencyProperty.Register("RockDensity_Unit", typeof(string), typeof(UserControl),
new FrameworkPropertyMetadata("kg/m3", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string RockDensity_Unit
{
get { return this.GetValue(RockDensityUnitProperty) as string; }
set { SetValue(RockDensityUnitProperty, value); }
}
XML
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}" SelectedItem="{Binding Path=RockDensity_Unit, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ucStep2}}, Mode=TwoWay}"
Background="#FFB7B39D" Grid.Row="5" Height="23" HorizontalAlignment="Left" Margin="401,61,0,0" Name="comboBox6" VerticalAlignment="Top" Width="84" Visibility="Hidden">
</ComboBox>
ERROR
Error 1 The type reference cannot find a public type named 'ucStep2'. Line 74 Position 194. This refers to the combobox ", "
after FindAncestor
DOUBT
The RockDensity_Unit CLR property in Step2InfoData is untouched.
Why is the code not able to find ucStep2 ? FYI, I think this may be relevant :
<UserControl x:Class="WellBore.ucStep2"
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:WellBore.Models"
xmlns:top="clr-namespace:WellBore"
mc:Ignorable="d"
d:DesignHeight="870" d:DesignWidth="700" MaxHeight="970" MinHeight="700" MaxWidth="600">
Ok, so let's get this binding working... first, I am using an item from your KgGmItems class to bind to the ComboBox. In this class you have a collection of string values to display in the drop down and a string property to bind to the ComboBox.SelectedItem... perfect! Now I'm assuming that you have an instance of this class in the Resources section called KgGmObj... let's keep it simple to start with:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding ValueSelected, Mode=TwoWay}" />
This is all you need to setup the binding between the ComboBox and your class. One thing to note though, is that when you try to set the selected item from your code, it will only work if you set it to one of the actual items in the collection... I think that this doesn't really count when using strings, but it's important to know this anyway. If you were setting a custom class as the type of objects in the ComboBox instead, then you could set the selected item like this:
ValueSelected = KgGmList.Where(item => item.Name == "NameOfObjectToMatch").Single();
Or better like this if you had a uniquely identifiable property:
ValueSelected = KgGmList.Where(item => item.Id == Id).Single()
With your string values, you should be able to set the selected item from code like this:
ValueSelected = "Some value";
UPDATE >>> Ok, so let's have another go... I think that I may have enough information to go on now. I think that you want something like this:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding RockDensity_Unit, Mode=TwoWay}" />
The problem with this is that you have set the DataContext of the ComboBox to your KgGmObj object. This means that the Framework is going to try to find a property named RockDensity_Unit in that object. I also see another potential problem in your definition of this property.
In order to bind from a UserControl xaml to its code behind, you need to use a DependencyProperty. You can find out how to implement these from the Dependency Properties Overview page at MSDN. So first, I would recommend that you implement your RockDensity_Unit property as a DependencyProperty.
Next, we have to find a way to that property from the ComboBox in the xaml... we can do that using a RelativeSource binding like this:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding RockDensity_Unit, RelativeSource={RelativeSource Mode=
FindAncestor, AncestorType={x:Type ucStep2}}, Mode=TwoWay}" />
Now, if you have a DependencyProperty to bind to the SelectedItem property and your UserControl class is named ucStep2, this should all work... let me know how it goes.
UPDATE 2 >>>
Your error is because you have to add an XML namespace at the top of your XAML file... something like this:
xmlns:YourNamespace="clr-namespace:ApplicationName.FolderNameContainingClass"
Then you use it to reference your class like this:
...AncestorType={x:Type YourNamespace:ucStep2} ...
Also, in your DependencyProperty declaration, you're supposed to supply the name the type of your control, not UserControl, so change
Register("RockDensity_Unit", typeof(string), typeof(UserControl),
to
Register("RockDensity_Unit", typeof(string), typeof(NameOfYourUserControl),
Clearly... replace 'NameOfYourUserControl' with the actual name of your class that extends the UserControl.
Use a Dictionary.
XAML
<ComboBox ItemsSource="{Binding Dict}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding Prop}"/>
Code Behind
public Dictionary< ValueType, string > Dict { get; private set; }
private ValueType _prop;
public ValueType Prop
{
get{ return _prop }
set
{
_prop = value;
NotifyPropertyChanged( "Prop" ); // Implement INotifyPropertyChanged
}
}
public ViewModel()
{
Dict = new Dictionary< ValueType, string >()
{
{ value1, string1 },
{ value2, string2 },
{ value3, string3 }
};
}

Categories