WPF MVVM firing code based on Tab SelectedValue, rather than SelectedIndex - c#

In WPF with MVVM it's easy to fire some code when the user changes the tab.
<TabControl Margin="0 5 5 5" Background="#66F9F9F9" SelectedIndex="{Binding TabIndex}">
And then in the ViewModel:
private int _tabIndex;
public int TabIndex
{
get { return _tabIndex; }
set
{
if(_tabIndex != value)
{
_tabIndex = value;
OnPropertyChanged("TabIndex");
if(value == 1)
{
//do something
}
}
}
}
But I'm vaguely uncomfortable with this. What if another developer happens along later and adds another tab in the "1" position. If this is application-critical code (which it is), things will break spectacularly.
Danger can be minimized with unit tests, of course. But it made me wonder: is this seen as bad practice? And is there a way of doing this that allows you to refer to the Tab with a string, rather than an int? I tried noodling with binding to the SelectedValue property, but nothing seemed to happen when the tabs were changed.

You could make a behavior for TabItem, listening for changes to the IsSelected dependency property, and raises a Command when the tab is selected. This can be extended to any number of tabs, each which invokes different commands in the viewmodel. You could also supply a command parameter for any optional context:
class TabSelectedBehavior : Behavior<TabItem>
{
public static readonly DependencyProperty SelectedCommandProperty = DependencyProperty.Register("SelectedCommand", typeof(ICommand), typeof(TabSelectedBehavior));
public ICommand SelectedCommand
{
get { return (ICommand)GetValue(SelectedCommandProperty); }
set { SetValue(SelectedCommandProperty, value); }
}
private EventHandler _selectedHandler;
protected override void OnAttached()
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TabItem.IsSelectedProperty, typeof(TabItem));
if (dpd != null)
{
_selectedHandler = new EventHandler(AssociatedObject_SelectedChanged);
dpd.AddValueChanged(AssociatedObject, _selectedHandler);
}
base.OnAttached();
}
protected override void OnDetaching()
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TabItem.IsSelectedProperty, typeof(TabItem));
if (dpd != null && _selectedHandler != null)
{
dpd.RemoveValueChanged(AssociatedObject, _selectedHandler);
}
base.OnDetaching();
}
void AssociatedObject_SelectedChanged(object sender, EventArgs e)
{
if (AssociatedObject.IsSelected)
{
if (SelectedCommand != null)
{
SelectedCommand.Execute(null);
}
}
}
}
XAML
<TabControl>
<TabItem Header="TabItem1">
<i:Interaction.Behaviors>
<local:TabSelectedBehavior SelectedCommand="{Binding TabSelectedCommand}"/>
</i:Interaction.Behaviors>
</TabItem>
<TabItem Header="TabItem2">
</TabItem>
</TabControl>
In a similar fashion you could also make a behavior for the TabControl, which turns the SelectionChanged event into a command, and pass the Tag object of the selected TabItem as command parameter.

As with all collection controls, the best way to maintain the selected item is to use the SelectedItem property. If you data bind a property of the relevant data type to the TabControl.SelectedItem property, then you'll still be able to tell which tab is selected and select a different one from the view model.
The only problem with this method is that you'll also need to use the TabControl.ItemsSource property to set up the TabItems:
<TabControl ItemsSource="{Binding YourDataItems}" SelectedItem="{Binding YourItem}" />
If you want to try this, then you should know that defining the TabItems can be a little bit confusing. Please refer to the answer from the How to bind items of a TabControl to an observable collection in wpf? question for help with that.

Related

How to run a method every time a TabItem is selected, in an MVVM application using Prism

I have been trying to implement this for a while and haven't been able to do it so far, despite having the feeling that this should be something easy.
The difficulty comes from the fact that I have implemented a WPF application using the MVVM pattern. Now, this is my first attempt at both the pattern and the framework, so it is almost guaranteed that I have made mistakes while trying to follow the MVVM guidelines.
My implementation
I have three Views with their respective ViewModels (wired using Prism's AutoWireViewModel method). The MainView has a TabControl with two TabItems, each of witch contains a Frame container with the Source set to one of the other two Views. The following code is an excerpt of the MainView:
<TabControl Grid.Row="1" Grid.Column="1">
<TabItem Header="Test">
<!--TestView-->
<Frame Source="View1.xaml"/>
</TabItem>
<TabItem Header="Results">
<!--ResultsView-->
<Frame Source="View2.xaml"/>
</TabItem>
</TabControl>
My problem
Every time that someone changes to a specific TabItem, I would like to run a method that updates one of the WPF controls included in that View. The method is already implemented and bound to a Button, but ideally, no button should be necessary, I would like to have some kind of Event to make this happen.
I appreciate all the help in advance.
You could for example handle the Loaded event of the Page to either call a method or invoke a command of the view model once the view has been loaded initially:
public partial class View2 : Page
{
public View2()
{
InitializeComponent();
Loaded += View2_Loaded;
}
private void View2_Loaded(object sender, RoutedEventArgs e)
{
var viewModel = DataContext as ViewModel2;
if (viewModel != null)
viewModel.YourCommand.Execute(null);
Loaded -= View2_Loaded;
}
}
The other option would be handle this in the MainViewModel. You bind the SelectedItem property of the TabControl to a property of the MainViewModel and set this property to an instance of either ViewModel2 or ViewModel2, depending on what kind of view you want to display.
You could then call any method or invoked any command you want on these. But this is another story and then you shouldn't hardcode the TabItems in the view and use Frame elements to display Pages. Please take a look here for an example:
Selecting TabItem in TabControl from ViewModel
Okay, so What I have done is Create a Custom Tab Control. I will write out step by step instructions for this, and then you can add edit to it.
Right click on your solution select add new project
Search For Custom Control Library
High Light the name of the class that comes up, and right click rename it to what ever you want I named it MyTabControl.
Add Prism.Wpf to the new project
Add a reference to the new project to where ever your going to need it. I needed to add to just the main application, but if you have a separate project that only has views then you will need to add it to that too.
Inherit your Custom Control From TabControl Like:
public class MyTabControl : TabControl
You will notice that there is a Themes folder in the project you will need to open the Generic.xaml and edit it. it should look like:
TargetType="{x:Type local:MyTabControl}" BasedOn="{StaticResource {x:Type TabControl}}" for some reason this will not let me show the style tags but they will need to be in there as well
Please review this code I got this from Add A Command To Custom Control
public class MyTabControl : TabControl
{
static MyTabControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyTabControl), new FrameworkPropertyMetadata(typeof(MyTabControl)));
}
public static readonly DependencyProperty TabChangedCommandProperty = DependencyProperty.Register(
"TabChangedCommand", typeof(ICommand), typeof(MyTabControl),
new PropertyMetadata((ICommand)null,
new PropertyChangedCallback(CommandCallBack)));
private static void CommandCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myTabControl = (MyTabControl)d;
myTabControl.HookupCommands((ICommand) e.OldValue, (ICommand) e.NewValue);
}
private void HookupCommands(ICommand oldValue, ICommand newValue)
{
if (oldValue != null)
{
RemoveCommand(oldValue, oldValue);
}
AddCommand(oldValue, oldValue);
}
private void AddCommand(ICommand oldValue, ICommand newCommand)
{
EventHandler handler = new EventHandler(CanExecuteChanged);
var canExecuteChangedHandler = handler;
if (newCommand != null)
{
newCommand.CanExecuteChanged += canExecuteChangedHandler;
}
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (this.TabChangedCommand != null)
{
if (TabChangedCommand.CanExecute(null))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
}
private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = CanExecuteChanged;
oldCommand.CanExecuteChanged -= handler;
}
public ICommand TabChangedCommand
{
get { return (ICommand) GetValue(TabChangedCommandProperty); }
set { SetValue(TabChangedCommandProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.SelectionChanged += OnSelectionChanged;
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (TabChangedCommand != null)
{
TabChangedCommand.Execute(null);
}
}
}
you will need to add the name space in your window or usercontrol like:
xmlns:wpfCustomControlLibrary1="clr-namespace:WpfCustomControlLibrary1;assembly=WpfCustomControlLibrary1"
and here is your control:
<wpfCustomControlLibrary1:MyTabControl TabChangedCommand="{Binding TabChangedCommand}">
<TabItem Header="View A"></TabItem>
<TabItem Header="View B"></TabItem>
</wpfCustomControlLibrary1:MyTabControl>
This is how I'd approach this sort of requirement:
View:
<Window.DataContext>
<local:MainWIndowViewModel/>
</Window.DataContext>
<Grid>
<TabControl Name="tc" ItemsSource="{Binding vms}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type local:uc1vm}">
<local:UserControl1/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:uc2vm}">
<local:UserControl2/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding TabHeading}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Grid>
</Window>
When it has a uc1vm it will be templated into usercontrol1 in the view.
I'm binding to a collection of viewmodels which all implement an interface so I know for sure I can cast to that and call a method.
Main viewmodel for window:
private IDoSomething selectedVM;
public IDoSomething SelectedVM
{
get { return selectedVM; }
set
{
selectedVM = value;
selectedVM.doit();
RaisePropertyChanged();
}
}
public ObservableCollection<IDoSomething> vms { get; set; } = new ObservableCollection<IDoSomething>
{ new uc1vm(),
new uc2vm()
};
public MainWIndowViewModel()
{
}
When a tab is selected, the setter for selected item will be passed the new value. Cast that and call the method.
My interface is very simple, since this is just illustrative:
public interface IDoSomething
{
void doit();
}
An example viewmodel, which is again just illustrative and doesn't do much:
public class uc1vm : IDoSomething
{
public string TabHeading { get; set; } = "Uc1";
public void doit()
{
// Your code goes here
}
}
I appreciate all of your input, but I found an alternative solution. Given the information given by #mm8, I took advantage of the Loaded event but in a way that does not require any code in the code behind.
My solution
In the View which I would like to give this ability to execute a method every time the user selects the TabItem that contains it, I added the following code:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding OnLoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
And then simply implemented a DelegateCommand called OnLoadedCommand in the View's respective ViewModel. Inside that command I call my desired method.
Please comment if you spot anything wrong with this approach! I chose to try this since it required the least amount of changes to my code, but I may be missing some vital information regarding problems the solution may cause.

CheckBox Checked event fires before bound collection updates

I have a custom control to show items with checkboxes inside a ComboBox. To realize this, I used a DataTemplate with a CheckBox. The ItemSource of the ComboBox uses a binding to a ObserableCollection<FilterValue> which contains my filter values. FilterValue is a custom class implementing INotifyPropertyChanged. The properties Content and IsChecked of the CheckBox use bindings as well to use the values of my list. This control will be used in Silverlight.
Binding itself works fine, as seen here:
The problem appears when I register the Checked or Unchecked event.
As soon as one of the check boxes changed its state, the event is fired as expected but at this moment, the value in the bound list is still not updated.
What I saw while debugging is that the Checked/Unchecked events are firing before the PropertyChanged event of the FilterValue.
This means that at the time the event is firing, I can't ask the list for all active (checked) filters. What could I do to achieve this?
FilterControl.xaml:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:local="clr-namespace:Controls" x:Class="Controls.FilterControl"
mc:Ignorable="d"
d:DesignHeight="45" d:DesignWidth="140">
<StackPanel x:Name="LayoutRoot">
<sdk:Label x:Name="LblFilterDescription" Content="-" />
<ComboBox x:Name="Filter" Width="120" ItemsSource="{Binding AvailableFilters, RelativeSource={RelativeSource FindAncestor, AncestorType=local:FilterControl}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=Text}" IsChecked="{Binding Path=IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Checked="FilterChanged" Unchecked="FilterChanged" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</UserControl>
FilterControl.xaml.cs:
public partial class FilterControl : UserControl
{
public delegate void FilterChangedHandler(object sender);
public event FilterChangedHandler OnFilterChanged;
public ObservableCollection<FilterValue> AvailableFilters { get; set; }
public List<string> AppliedFilters
{
get
{
return new List<string>(AvailableFilters.Where(filter => filter.IsChecked).Select(filter => filter.Text));
}
}
public FilterControl()
{
InitializeComponent();
AvailableFilters = new ObservableCollection<FilterValue>();
}
public bool AddFilterValue(string filterValue)
{
bool found = false;
foreach (FilterValue f in AvailableFilters)
{
if (f.Text == filterValue)
{
found = true;
break;
}
}
if (!found)
AvailableFilters.Add(new FilterValue() { IsChecked = false, Text = filterValue });
return found;
}
private void FilterChanged(object sender, RoutedEventArgs e)
{
//Here if I check AvailableFilters, the value is not changed yet.
//PropertyChanged allways fires after this, what makes me unable to
//get all currently applied filters (checked items)...
}
}
FilterValue:
public class FilterValue : INotifyPropertyChanged
{
private bool _IsChecked;
private string _Text;
public bool IsChecked
{
get { return _IsChecked; }
set
{
_IsChecked = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsChecked"));
}
}
public string Text
{
get { return _Text; }
set
{
_Text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Text"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So, as I tried to reproduce this behavior, I realized that this appears to be a behavior that only occurs like that in Silverlight. If you try this example on WPF, the Changed fires after the bound property is updated. So you can just access your AppliedFilters property in the FilterChanged method and it will reflect the actual current situation. On Silverlight though, not so much. Even worse, this behavior didn’t even appear to be consistent to me. I did encounter situations in which the event fired after the property has been updated (resulting in the expected output).
A way to get around this is to clean up your component logic. If you look at it, you are mixing two different concepts: Event-driven UI logic, and clear data binding. Of course, doing it “properly” has multiple effects you likely cannot just ensure in an existing project, but you can at least try to get in the right direction here which should then also solve this issue.
So your logic right now uses data binding to provide the data for the view, and to reflect changes of the displayed items. But you are using events on the item level to perform additional logic depending on the former changes. As we have seen, the order of execution appears not be guaranteed across platforms, so it’s best to avoid having to rely on it.
In this case, you should have your data be the source of truth and make changes in the data tell you when applied filters change. You’re already halfway there by having an ObservableCollection and items that implement INotifyPropertyChanged. Unfortunately, an observable collection will only notify you about changes to the collection but not to changes to the contained items. But there are multiple solutions to expand the collection to also look at the items inside the collection.
This related question covers exactly that topic and there are multiple ideas on how to expand the observable collection for exactly that behavior. In my case, I have used the FullyObservableCollection implementation by Bob Sammers.
All you have to do for that is to change your ObservableCollection<FilterValue> into a FullyObservableCollection<FilterValue> and subscribe to the ItemPropertyChanged event:
AvailableFilters = new FullyObservableCollection<FilterValue>();
AvailableFilters.ItemPropertyChanged += AvailableFilters_ItemPropertyChanged;
In that event handler, you will then correctly see the proper behavior.

Change binding when focus changes

I have an application that has several treeviews and one propertygrid (from the extended WPF toolkit). The goal is to display the properties of the selected item. I'm fairly new to WPF so I started with one treeview and bind the propertygrids selected object like this
<xctk:PropertyGrid x:Name="xctkPropertyGrid"
Grid.Column="2"
ShowSearchBox="False"
ShowSortOptions="False"
SelectedObject="{Binding ElementName=actionsTreeView, Path=SelectedItem, Mode=OneWay}">
</xctk:PropertyGrid>
This seems to work fine. But it off course binds to actionsTreeView all the time. What I would really need is an update of that propertygrid when the focus changes to another selecteditem in another treeview. I have achieved my goal using the SelectedItemChanged of each treeview and set the propertygrids selectedobject like so. Is this somehow possible using databinding and triggers. My solution adds some code behind and tight coupling and that doesn't feel very MVVM.
kind regards,
Jef
Ok, here's how I ended up solving my problem:
Each treeview is bound to a viemodel property on the main viewmodel. I also created a SelectedItem property on the main viewmodel like this to which the propertygrid's SelectedObject is bound:
private object selectedItem;
public object SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
I then attach a behavior to each treeview that updates this SelectedItem:
public class UpdateSelectedItemBehavior : Behavior<TreeView>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.GotFocus += AssociatedObject_GotFocus;
this.AssociatedObject.SelectedItemChanged += AssociatedObject_SelectedItemChanged;
}
void AssociatedObject_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
ViewModels.MainViewModel mainViewModel = AssociatedObject.DataContext as ViewModels.MainViewModel;
if (mainViewModel != null)
{
mainViewModel.SelectedItem = AssociatedObject.SelectedItem;
}
}
void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
{
ViewModels.MainViewModel mainViewModel = AssociatedObject.DataContext as ViewModels.MainViewModel;
if (mainViewModel != null)
{
mainViewModel.SelectedItem = AssociatedObject.SelectedItem;
}
}
}

Event Handlers on DataTemplate inside ItemsControl

I have an ItemsControl so that I can display multiple instance of the same template. I need to be able to execute code on event handlers so that I can tell controls apart.
For example: I have a list of groceries, so my DataTemplate contains a "buy" Button for each food. I want to bind said button to code and tell which button was pressed.
How can I accomplish that, considering I'm using MVVM design pattern
** XAML :**
<ItemsControl ItemsSource="{Binding MyItemList}">
<ItemsControl.ItemsTemplate>
<DataTemplate>
<Button Content="Buy" />
</DataTemplate>
</ItemsControl.ItemsTemplate>
</ItemsControl>
So, MyItemList is a List<MyItem> instance. The DataTemplate contains controls that modify values or execute code not present in MyItem:
I have read a lot of articles on biding templates to commands, but I cant find one that uses a list of items.
You need to bind the Button to a Command your ItemsControl's DataContext.
Search for Command in WPF : ( A Common implementation ) :
public class RelayCommand<T> : IRelayCommand
{
private Predicate<T> _canExecute;
private Action<T> _execute;
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
private void Execute(T parameter)
{
_execute(parameter);
}
private bool CanExecute(T parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public bool CanExecute(object parameter)
{
return parameter == null ? false : CanExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
var temp = Volatile.Read(ref CanExecuteChanged);
if (temp != null)
temp(this, new EventArgs());
}
}
In your ViewModel ( The ItemsControl's DataContext , I Hope :) )
private RelayCommand<FoodItem> _addToGroceriesCommand;
public ICommand AddToGroceriesCommand
{
get
{
if (_addToGroceriesCommand == null)
{
_addToGroceriesCommand = new RelayCommand<FoodItem>(OnAddToGroceries);
}
return _addToGroceriesCommand;
}
}
public void OnAddToGroceries(FoodItem newItem)
{
}
XAML :
<ItemsControl ItemsSource="{Binding MyItemList}">
<ItemsControl.ItemsTemplate>
<DataTemplate>
<Button Content="Buy"
Command="{Binding Path=DataContext.AddToGroceriesCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}" />
</DataTemplate>
</ItemsControl.ItemsTemplate>
</ItemsControl>
You should never use events in DataTemplates this will make you use casting and then blow a hole in the whole MVVM pattern. A button has the Command property and you should Bind that property to a command inside your MyItem ViewModel.
If you still need to use an event (for instance you cant bind MouseDown to a command) you shoudl use the EventToCommadn Behaviour which allows you to bind an event to a command. You can read about it here: http://msdn.microsoft.com/en-us/magazine/dn237302.aspx
There are several things you might do.
<Button Content="Add" Click={Click} Tag="{Binding .}" DataContext="{Binding .}" />
DataContext="{Binding .} - sets the whole VM instance to property. You can do the same thing with the Tag property. Sometimes it is usefull to use Tag for these purposes. You can user either of them. Both will work.
public void Click(...)
{
var control = sender as FrameWorkElement;
if(control!= null)
{
var myVM = control.DataContext as MyViewModel;
myVM.DoSomethingWithMyVM();
}
}
You can create a usercontrol that would contain the grid and in the grid you reference the custom usercontrol. That's very flexible. In it's ButtonEventhandler you can access the datacontext and do what you need with it. this is much easier, but you'll have more work with notifications to parrent objects. This is better if you want to reuse this control.
Another thing you can do is to set the datacontext of the button to the whole ViewModel. A last effort solution would be to set the Tag of the button to the whole ViewModel. Better if you are not planing to reuse it.
You can also use this as a resource from the resourceDictionary.

Show chosen menu item

Hy,
I have a menu with a few menu items. I have various other elements like a treeview and some controls. When I open the program all elements in the menu are available. But the first step I have to do is to connect to the server. So all the the other elements shouldn't available till there is made a connection via the connection menu item.
Then I want to show only menu items if a special tree view (for instance the whole item structure) item is choosen for instance all topics. For instance there should be special menu items available if I click a treeview entry in the menu.
Is it possible to accomplish this in xaml?
Update1:
MainWindow.xaml
Title="Service Bus Visualizer" Height="680" Width="1200" Name="Root"
<MenuItem Header="_Read File" Name="readFile" Click="MenuItemReadFile" HorizontalAlignment="Right" Width="187" IsEnabled="{Binding Path=DataContext.IsMonitoring, ElementName=Root}">
<MenuItem.Icon>
<Image Source="Icons/Open.ico" Width="16" Height="16" />
</MenuItem.Icon>
</MenuItem>
MainWindow.xaml.cs
public bool IsMonitoring
{
get
{
return isMonitoring;
}
set
{
isMonitoring = value;
RaisePropertyChanged("IsMonitoring");
}
}
private bool isMonitoring;
public MainWindow()
{
InitializeComponent();
this.IsMonitoring = false;
this.DataContext = this;
Application.Current.MainWindow = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
ConnectionWindow.xaml.cs
MainWindow mainWindow = Application.Current.MainWindow as MainWindow;
mainWindow.IsMonitoring = true;
I get no error on the output window but it doesn't work?
Update2:
I have a second parameter which is a ObservableCollection.
MainWindow.xaml
<ListBox Grid.Row="3" Name="Logger" ItemsSource="{Binding Path=DataContext.LoggingList, ElementName=Root}" DisplayMemberPath="Message" IsSynchronizedWithCurrentItem="True" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Visible" SelectionChanged="BringSelectionIntoView">
</ListBox>
MainWindow.xaml.cs
public static ObservableCollection<Log> LoggingList { get; set; }
public MainWindow()
{
LoggingList = new ThreadSafeObservableCollection<Log>();
this.IsMonitoring = false;
this.DataContext = this;
Application.Current.MainWindow = this;
InitializeComponent();
}
Log.cs
public class Log : INotifyPropertyChanged
{
public string Message {
get
{
return message;
}
set
{
message = value;
NotifyPropertyChanged("Message");
}
}
public string message;
public Log()
{
}
public Log(string message)
{
this.Message = message;
NotifyPropertyChanged("Message");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
Best regards
First, you have two options as far as "availability" is concerned. The "IsEnabled" property, and the "Visible" property. "IsEnabled" is a bool, and determines if the user can click/select/interact with a given element. Generally speaking, if this property is set to false the element will appear "greyed out".
Changing Visibility will make the element appear/disappear entirely. When set to "Visible" (this is actually an enum), it appears normally. When set to "Hidden", the space for it is reserved on the UI, but you can't actually see it. When set to "Collapsed" you cannot see it and no space is reserved for it in the layout.
For your first requirement (waiting to connect to the server), I would use IsEnabled bound to a "IsConnected" property like so:
IsEnabled="{Binding IsConnected}"
That would go on each item that needs to have this behavior.
The "context-specific" menu items are a bit more complicated, but the basic idea would be a binding on Visible for each of the context sensitive items like:
Visible="{Binding Path=SelectedItem, ElementName=MyTreeView, Converter={StaticResource SelectedItemToVisibilityConverter}, ConverterParameter={x:Type ChildItem}"
I am assuming that each items visibility depends on what type of item is selected (child or parent), you should be able to extend the example if I was wrong. The converter would then look like:
public class SelectedItemToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((parameter as Type).IsInstanceOfType(value))
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(...)
{
return Binding.DoNothing;
}
}
Please let me know if I can clarify anything. Hopefully that gives you a good starting point for what you are trying to do.
Update:
Looking at your code I see a couple potential problems:
IsMonitoring is declared as a public field. Binding only works with public properties. This property needs to raise the PropertyChanged event for it to work.
In "MainWindow.xaml.cs" you are setting the DataContext multiple times. This isn't how DataContext works in WPF. You need to set it to one object (your ViewModel) that contains all the properties you are interested in binding to. While it is considered bad practice, you could write this.DataContext = this to get it working before you build a ViewModel class.
The IsMonitoring field is declared in your "MainWindow.xaml.cs" file. First, this should be in a view model. Second, the binding is looking for that property on the MenuItem class (likely because it is in some sort of ItemsControl). If you want it on the root data context, give your window some name (like "Root") and use the following binding:
"{Binding Path=DataContext.IsMonitoring, ElementName=Root}"
Hopefully that makes sense. Let me know if I can help further!

Categories