Silverlight Combobox - items with a command - silverlight

What would be the best way to get the elements of a combobox to each support a Command and CommandParameter?
I'd like to implement the Theme Chooser shown toward the bottom of this blog post, except with a combo box instead of a context menu. I'd need each element of the combobox to support a Command and CommandParameter, and I'd like it to just be plain text, as the combo below is.
<ComboBox>
<ComboBox.Items>
<TextBlock>A</TextBlock>
<TextBlock>B</TextBlock>
<TextBlock>C</TextBlock>
</ComboBox.Items>
</ComboBox>
I tried hyperlinks, but the main problem there is that when you click directly onto the link text, the combo box does not close.
Is there an easy way to do this?
EDIT
Ok, well the specific goal that I said I wanted to achieve—having a combo change the SL Toolkit theme—is trivially accomplished. I can simply bind the selected item of the combo to a ViewModel property that then exposes the appropriate themeuri which my SL Toolkit theme can bind to, or, since this is purely a UI activity with no business logic, I can just catch the combobox item changed event, and update my themeUri from there.
I am curious though, is there a good way to bind each combo box item to a command with a command parameter? Using a Hyperlink as each comboboxItem seemed promising, but that prevents the CB from closing after you click on an item when you click the actual hyperlink.

You could Bind the selected item to your ViewModel and then the setter would trigger when the Theme was changed.
Xaml:
<ComboBox SelectedItem="{Binding SelectedTheme, Mode=TwoWay}" ItemsSource="{Binding Themes}" />
CodeBehind:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
DataContext = new MainPageViewModel();
}
}
ViewModel:
public class MainPageViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> Themes { get; set; }
private string _selectedTheme;
public string SelectedTheme
{
get { return _selectedTheme; }
set
{
_selectedTheme = value;
// Change the Theme
RaisePropertyChanged("SelectedTheme");
}
}
public MainPageViewModel()
{
Themes = new ObservableCollection<string>();
Themes.Add("Red");
Themes.Add("Green");
Themes.Add("Blue");
}
}

Related

MVVM Light and combobox

I am new to WPF and MVVM Light, I would appreciate if you could help me :-)
I would like to know how to implement a combobox with MVVM Light to do the following:
1) Select an item in the combobox
2) Based on the value selected, change other text fields in the GUI.
Thank you for your help.
Romain
Well:
View:
<ComboBox ItemsSource="{Binding SourceData}" SelectedItem="{Binding SelectedSourceData,Mode=TwoWay}"/>
<TextBlock Text="{Binding SelectedDataInTextFormat}"/>
ViewModel:
public class ViewModel:ViewModelBase
{
public ObservableCollection<Foo> SourceData{get;set;}
public Foo SelectedSourceData
{
get{return _selectedFoo;}
set{_selectedFoo=value;
RaisePropertyChanged("SelectedSourceData");
SelectedDataInTextFormat=Foo.ToString();
}
public string SelectedDataInTextFormat
{
get{return _selectedDataInTextFormat;}
set{_selectedDataInTextFormat=value;
RaisePropertyChanged("SelectedDataInTextFormat");
}
}
Basically, to ensure that your view model is able to receive the updated selected item from the combobox make sure the SelectedItem binding is set to Mode=TwoWay. To ensure that you're pushing data from the viewmodel to the view when a change occuers in the viewmodel make sure you call the RaisePropertyChanged helper class for the property you want updated in the view.

Bubbling up WPF Datagrid Item events

I've a datagrid with an ItemsSource of ObservableCollection (OC) of objects. When an item's property changes, I want to work on the OC itself .
E.g. I've an item which is approved for uploading to our database. However, I need to loop through the OC to check if other items exist in the collection which already fit the set criteria, so that I may actually not have to upload the selected item.
On the datagrid, when I tick the checkbox of an item, it will change the boolean value (e.g. "IsToUpload") of the item, and an event should trigger on the property change.
I'm assuming I will then need to 'bubble up' my event notifications to the datagrid/mainwindow class, where I can then work on the OC. How may I do this, and if this is not the correct way, what should I be doing?
I've followed Aran Mulholland's class structure to colour my rows dynamically: Coloring WPF DataGridRows one by one
So my class structure is roughly as follows:
MainWindow -> DataGrid
-> ObservableCollection<ItemObjectViewModel:NotificationObject>
ItemObject : INotifyPropertyChanged //this class is where I
//store my item variables. It is referenced through properties
//in the ItemObjectViewModel.
Event bubling \ routing etc works for dependency objects in a visual \ logical tree. Your NotificationObject is not a dependency object and neither is it hosted in the visual tree.... What we have in visual tree are the checkboxes (that are bound to your NotificationObject).
Non MVVM
In you DataGrid you would have to Tag your Checkboxes with some identification and then use ButtonBase.Click="" event at datagrid level which will be handled for any click event bubbled for any button based eleemnt (such as buttons, menuitems, togglebuttons, checkboxes, radioboxes, comboboxes) that gets clicked in the entire visual tree of the datagrid.
In the handler verify if the e.OriginalSource is a checkbox and that its Tag is same as the identification value we have set in the XAML of the datagrid. That way we know that the CheckBox is clicked.
E.g.
<DataGrid AutogenerateColumns="False"
ItemsSource="{Binding NotificationObjectCollection}"
ButtonBase.Clicked="OnNotificationCheckBoxClicked">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsClicked}"
Header="Click?">
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Tag" Value="IsClickCheckBox" />
</Style>
</DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
private void OnNotificationCheckBoxClicked
(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is CheckBox)
{
if (((CheckBox)e.OriginalSource).Tag == "IsClickCheckBox")
{
var notificationObject
= ((CheckBox)e.OriginalSource).DataContext
as NotificationObject;
if (notificationObject.IsClicked) { }
else { }
}
}
}
MVVM
The only way MVVM can notify the ancestor object in the visual is by using Command execution as the underlying NotificationObject gets checked (setter is called) we execute the command supplied to the NotificationObject.
Use the weak reference based RelayCommand or DelegateCommand (as available on the internet) for this purpose.
Add a new NotificationObject constructor
private ICommand _isClickedCommand;
public NotificationObject(ICommand isClickedCommand)
{
_isClickedCommand = isClickedCommand;
}
private bool _isClicked;
public bool IsClicked
{
get
{
return _isClicked;
}
set
{
if (_isClicked != value)
{
_isClicked = value;
OnPropertyChanged("IsClicked");
isClickedCommand.Execute(this);
}
}
}
Using the notification object
public class ItemObjectViewModel
{
private DelegateCommand<NotificationObject>
_notificationObjectClickedCommand
= new DelegateCommand<NotificationObject>(
OnNotificationObjectCommandExecute);
....
private void PopulateCollection()
{
NotificationObjectCollection
= new ObservableCollection<NotificationObject>();
NotificationObjectCollection.Add(
new NotificationObject(_notificationObjectClickedCommand));
}
private void OnNotificationObjectCommandExecute(
NotificationObject notificationObject)
{
if (notificationObject.IsClicked) { }
else { }
}
}
You can also achieve the ICommand based behavior in non MVVM scenario by using 'RoutedCommand'
Let me know if this helps...

TabControl ItemTemplate without ItemsSource

I am doing a WPF application with a TabControl. At the beginning I had a TabControl bound to ObservableCollection of TabBase items, where TabBase is a base class for tab viewmodel:
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Tabs}"
ItemTemplate="{StaticResource ClosableTabTemplate}"
...
public ObservableCollection<TabBase> Tabs { get; private set; }
...
public abstract class TabBase : ViewModelBase
...
public abstract class ViewModelBase : INotifyPropertyChanged
{
public virtual string DisplayName { get; protected set; }
...
<DataTemplate x:Key="ClosableTabTemplate">
<DockPanel Width="120">
<Button
Command="{Binding Path=CmdClose}"
Content="X"
/>
<ContentPresenter
Content="{Binding Path=DisplayName}">
</ContentPresenter>
</DockPanel>
</DataTemplate>
But I've faced with an issue when I switch tabs it looks like current tab is being created each time, even if it was already opened before. Searching thru StackOverflow I've found the solution here with reference to here. I've replaced using of declarative ItemsSource with dynamic creation of tabs from code. Tabs switching performance issue was resolved, but tab headers have lost link to template, so instead of tab header with caption and close button I see just a little tab header without anything. Playing a bit with tab creation code, I was able to restore tab size and close button, but without binding - there is no caption and close button doesn't work (5 lines with item.Header restored original tab size):
private void AddTabItem(TabBase view)
{
TabItem item = new TabItem();
item.DataContext = view;
item.Content = new ContentControl();
(item.Content as ContentControl).Focusable = false;
(item.Content as ContentControl).SetBinding(ContentControl.ContentProperty, new Binding());
item.Header = new ContentControl();
(item.Header as ContentControl).DataContext = view;
(item.Header as ContentControl).Focusable = false;
(item.Header as ContentControl).SetBinding(ContentControl.ContentProperty, new Binding());
item.HeaderTemplate = (DataTemplate)FindResource("ClosableTabTemplate");
tabControl.Items.Add(item);
}
The question is, how can I make ItemTemplate working for TabControl without ItemsSource binding?
When you explicitly set your item.Header to a ContentControl, the HeaderTemplate is now using that object as its DataContext. Normally, the Header property would get your ViewModel and a ContentPresenter would take that (non-Visual) object and apply the HeaderTemplate to it. You've now pushed your ViewModel down a level in the hierarchy so the template is not being applied at the same place as the data. Moving either one should fix the Binding issues but one or the other may work better for your situation:
item.Header = view;
or
(item.Header as ContentControl).ContentTemplate = (DataTemplate)FindResource("ClosableTabTemplate");

How to have a button in a datagrid template that will remove the item when clicked

I would like to use a datatemplate for my datagrid columns and have a button for each item. I would like the item to be removed if the user clicks the button. I am using the MVVM pattern. How would I accomplish this?
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Width="50" Content="Remove" Command="{Binding RemoveItemCommand}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
public class ItemViewModel
{
public ItemViewModel()
{
RemoveCommand = new MyCommand(Remove);
}
public event EventHandler ItemRemoved;
public ICommand RemoveCommand { get; private set; }
private void Remove()
{
// Whatever it takes to remove item from your data store
service.Remove(this.Data);
var removeItem = ItemRemoved;
if (removeItem != null)
removeItem(this, EventArgs.Empty);
}
}
public class ListViewModel
{
public ListViewModel(IEnumerable<ItemViewModel> items)
{
ItemVMs=new ObservableCollection<ItemViewModel>(items);
foreach (var item in ItemVMs)
item.ItemRemoved += RemoveSelectedItem;
}
public ObservableCollection<ItemViewModel> ItemVMs { get; private set; }
private void RemoveSelectedItem(object sender, EventArgs e)
{
var item = sender as ItemViewModel;
item.ItemRemoved -= RemoveSelectedItem;
ItemVMs.Remove(item);
}
}
Each item's RemoveCommand would be bound to its button in your DataGrid. It sounds like you already have that part done. Make the ListViewModel's ItemVMs property the data source for your DataGrid.
The View is responsible for this. You can simply use codebehind to control the visibility of UI elements in response to user actions in the UI.
Sometimes, it is better to be practical than be rigidly dogmatic.
Well, now that you have edited your question, it becomes a completely different matter.
Your DataGrid should be bound to a collection of items.
Your button should be bound to a command on the ViewModel, and the CommandParameter should be the Model that particular row is bound to.
<DataTemplate>
<Button Content="Remove"
Command="{Binding DataContext.RemoveItemCommand,
ElementName=theWindow}"
CommandParameter="{Binding}" />
</DataTemplate>
Note some important things here. We need, from within the template, to bind to an ICommand on the ViewModel. The ViewModel is the DataContext of the Window. In this example, the window is named 'theWindow' (x:Name="theWindow"). Since the source of the Binding is the window, the Path must point to the ViewModel in the DataContext property on that Window.
We pass the current Model the DataGrid row is bound to into the command. This way, it is triival to remove it from the collection in the ViewModel.
public ObservableCollection<Model> Items {get;set;}
public ICommand RemoveItemCommand {get;set;}
// this method is called when RemoveItemCommand.Execute is called!
public void Execute(object parameter)
{
Items.Remove(parameter as Model);
}
This assumes you're using one of the standard delegated ICommand implementations out there. You can see how this is trivial to implement, and since the collection is an observable one, once you click the button and the Model is removed, the DataGrid will be notified of the change in the collection and remove that row.
You're probably better off using the standard routed events on the Click event of the button instead of a Command. The click event will allow you to retrieve the information about what control was clicked, and then you can also easily retrieve the parent of the button, to delete that item.

Activation/deactivation of toolbar buttons using Prism

I’m in the process of learning the Prism framework and I’ve come along way already. But I was wondering about how to create toolbars (and context menus) where each module can register their own buttons.
For this example I want all buttons to reside in the same ToolBar control which is located in my Shell. The ToolBars ItemsSource binds to a ToolBarItems property of type ObservableCollection<FrameworkElement> in the view model. Elements can be added to this collection using a ToolBarRegistry service. This is the ViewModel:
public class ShellViewModel
{
private IToolBarRegistry _toolBarRegistry;
private ObservableCollection<FrameworkElement> _toolBarItems;
public ShellViewModel()
{
_toolBarItems = new ObservableCollection<FrameworkElement>();
_toolBarRegistry = new ToolBarRegistry(this);
}
public ObservableCollection<FrameworkElement> ToolBarItems
{
get { return _toolBarItems; }
}
}
Note that the collection of type FrameworkElement will be refactored to be of a more concrete type if this turns out to be the correct solution.
My ToolBarRegistry has a method to register image buttons:
public void RegisterImageButton(string imageSource, ICommand command)
{
var icon = new BitmapImage(new Uri(imageSource));
var img = new Image();
img.Source = icon;
img.Width = 16;
var btn = new Button();
btn.Content = img;
btn.Command = command;
_shellViewModel.ToolBarItems.Add(btn);
}
I call this method from my OrderModule and the buttons show up correctly. So far so good.
The problem is how I can control when these buttons should be removed again. If I navigate to a view in another module (and sometimes another view in the same module), I want these module-specific buttons to be hidden again.
Do you have any suggestions on how to do this? Am I approaching this problem the wrong way, or can I modify what I already have? How did you solve this problem?
I would not insert Button instances in the ObservableCollection. Think about this approach instead:
Create ViewModel for the toolbar buttons
class ToolBarButtonViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged implementation to be provided by you
public string ImageSource { get; set; }
public ICommand Command { get; set; }
public bool IsVisible { get; set; }
}
Then of course change the type of ToolBarItems to a collection of these.
In your ShellView, add a DataTemplate for ToolBarButtonViewModel and bind the ItemsSource of whatever your toolbar control is to the collection of ViewModels, for example:
<DataTemplate>
<Button Command="{Binding Command}">
<Button.Content>
<Image Source="{Binding ImageSource}" />
</Button.Content>
</Button>
</DataTemplate>
You can now bind Button.Visibility to IsVisible with a BooleanToVisibilityConverter to solve your immediate problem.
As an added bonus, you can also:
Change the visual appearance of the toolbar buttons entirely from XAML
Bind any property of the visual tree for a toolbar button to corresponding properties on the ToolBarButtonViewModel
Update
The mechanism for enabling/disabling buttons depends on specifics of your application. There are many options -- here are a few (keep this chart in mind while reading):
Implement INavigationAware in your Views or ViewModels and enable/disable buttons as required
Attach handlers to the events of IRegionNavigationService of the region(s) of interest and have the handlers enable or disable buttons
Route all navigation through your own code (CustomNavigationService) and decide what to do inside it

Resources