Combobox display value in Silverlight - silverlight

I have ComboBox with CheckBoxes for items.
When user checks or uncheckes boxes I want the selected values to be displayed in the ContentPresenter separated by comma.
At the the moment I have overridden ContentPresenter:
<ContentPresenter x:Name="ContentPresenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
ContentTemplate="{StaticResource SelectedOperationsText}"/>
ContentPresenter is a part of ComboBox style by default.
Any hints on how to implement this feature?
ComboBox ItemTemplate is implemented like this:
<DataTemplate x:Key="ComboItemTemplate">
<Grid HorizontalAlignment="Left">
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Text}"/>
</Grid>
</DataTemplate>

This solution isn't ideal (for example, you can create custom control template for control inherited from combobox), but it works.
Xaml
<my:MyComboBox Width="180" ItemsSource="{Binding TestItems}" Text="{Binding SelectedItemsText}">
<my:MyComboBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Left">
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" Content="{Binding Text}"/>
</Grid>
</DataTemplate>
</my:MyComboBox.ItemTemplate>
</my:MyComboBox>
Hack of the combobox:
public class MyComboBox : ComboBox
{
private ContentPresenter selectedContent;
public MyComboBox()
{
this.DefaultStyleKey = typeof(ComboBox);
}
public override void OnApplyTemplate()
{
this.selectedContent = this.GetTemplateChild("ContentPresenter") as ContentPresenter;
this.RefreshContent();
base.OnApplyTemplate();
this.SelectionChanged += (s, e) =>
{
//Cancel selection
this.SelectedItem = null;
this.RefreshContent();
};
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyComboBox),
new PropertyMetadata(null, new PropertyChangedCallback((s,e)=>((MyComboBox)s).RefreshContent())));
private void RefreshContent()
{
if (this.selectedContent != null)
{
var tb = (TextBlock)this.selectedContent.Content;
tb.Text = this.Text;
}
}
}
MainViewModel
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
this.InitializeTestItems();
}
public void InitializeTestItems()
{
this.TestItems = new List<TestItemModel>{
new TestItemModel{IsChecked=true, Text="first"},
new TestItemModel{IsChecked=false, Text="second"},
new TestItemModel{IsChecked=false, Text="third"}};
this.RefreshSelectedItemsText();
foreach (var item in this.TestItems)
item.CheckChanged += (s, e) => this.RefreshSelectedItemsText();
}
private void RefreshSelectedItemsText()
{
SelectedItemsText = string.Join(", ", this.TestItems.Where(ti => ti.IsChecked).Select(ti => ti.Text));
}
public List<TestItemModel> TestItems { get; set; }
private string selectedItemsText;
public string SelectedItemsText
{
get { return selectedItemsText; }
set
{
selectedItemsText = value;
OnPropertyChanged("SelectedItemsText");
}
}
}
4.ItemViewModel
public class TestItemModel
{
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (CheckChanged != null)
CheckChanged(this, null);
}
}
public string Text { get; set; }
public event EventHandler<EventArgs> CheckChanged;
}

I did not understand what you meant by "the ContentPresenter".
If you want a combox box, with the list of selected items as its text, I can explain how my son (who's not in SO) did it:
He put a grid with a ComboBox followed by a TextBlock. The ItemTemplate of the ComboBox includes a check box with a handler for the Checked and UnChecked events. In these events, he recomputed the Text property of the TextBlock, based on the selected state of the check boxes.
Here is the XAML:
<Grid Name="LayoutRoot">
<ComboBox ItemsSource="{Binding Path=SitesList}" Name="CBsites" DropDownOpened="ComboBox_DropDownOpened" DropDownClosed="ComboBox_DropDownClosed">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=Location}" Checked="SiteCheckBox_Checked" Unchecked="SiteCheckBox_Unchecked" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Name="TXTselected" IsHitTestVisible="False" VerticalAlignment="Center" Margin="6,0,0,0" />
</Grid>
I think one can do it without the TextBlock. Hopefully, this can put you in the right direction.

I have created a codeplex project here: codeplex inspired by this blog and a number other ones, please check it out and improve it etc. Hopefully this or something like it will find it's way into the toolkit...
I prefer not needing a selection boolean in the bound data so i brought a bindable SelectedItems

Related

How to set specific item in a ListView using MVVM?

I have a ListView of Button elements like this:
<ListView ItemsSource="{Binding DummyModelList}" SelectedItem="{Binding SelectedItem}">
<ListViewItem >
<Button Name="test" Grid.Row="0" Grid.Column="10" Grid.ColumnSpan="4" Grid.RowSpan="4" VerticalAlignment="Center" Background="Transparent" Command="{Binding DataContext.TestCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}}">
<Button.Template>
<ControlTemplate>
<Grid RenderTransformOrigin="0.5,0.5" x:Name="bg">
<Image Source="{Binding DataContext.SlidOnOffImg , RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}}"/>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</ListViewItem >
In my ViewModel:
public class MyViewModel:BindableBase
{
private ObservableCollection<Slide> dummyModelList;
public ObservableCollection<Slide> DummyModelList
{
get { return dummyModelList; }
set
{
dummyModelList = value;
OnPropertyChanged(() => DummyModelList);
}
}
public string SlidOnOffImg { get; set; }
private int selectedItem;
public int SelectedItem //this property stores currently selected item from thumbnails list
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged(() => SelectedItem);
}
public CompositeCommand TestCommand { get; set; }
public MyViewModel(IRegionManager regionManager, IEventAggregator eventAggregator) //constructor
{
dummyModelList = new ObservableCollection<ISlide>() { new Slide(), new Slide(), new Slide()};
TestCommand = new CompositeCommand();
TestCommand.RegisterCommand(new DelegateCommand(ChangeImage));
}
private void ChangeImage()
{
if (dummyModelList.ElementAt(selectedItem).SlideExcluded)
dummyModelList.ElementAt(selectedItem).SlideExcluded = false;
else
dummyModelList.ElementAt(selectedItem).SlideExcluded = true;
SlidOnOffImg = dummyModelList.ElementAt(selectedItem).SlideONOFFImgPath;
OnPropertyChanged(() => SlidOnOffImg);
}
}
In my model:
public class Slide : ISlide
{
public bool SlideExcluded { get; set; }
public string SlideONOFFImgPath
{
get
{
if(SlideExcluded)
return "/Assets/Visible_OFF.png";
else
return "/Assets/Visible_ON.png";
}
}
}
My goal is to have button's image to toggle between two image paths on button click. It works, but the problem is that all the buttons in the list change the image path on some button click. I want only the one that is clicked to change the image path.
How to solve this?
You need to be binding to SelectedIndex instead of SelectedItem in your XAML. SelectedIndex holds a reference to the index for the item in the list, SelectedItem holds a reference to the object in the list. Either that, or change your view model to work with the actual object rather than the index.

DependencyProperty.RegisterAttached and Multiple Instances

I'm working on a WPF MVVM application. I'm looking to databind a WebBrowser control to a view model which is in turn bound to a Tab. Following the advice in this article, I created a static helper class consisting of a static DependancyProperty:
public static class WebBrowserHelper
{
public static readonly DependencyProperty BodyProperty =
DependencyProperty.RegisterAttached("Body", typeof(string), typeof(WebBrowserHelper), new PropertyMetadata(OnBodyChanged));
public static string GetBody(DependencyObject dependencyObject)
{
return (string)dependencyObject.GetValue(BodyProperty);
}
public static void SetBody(DependencyObject dependencyObject, string body)
{
dependencyObject.SetValue(BodyProperty, body);
}
private static void OnBodyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
string newValue = (string)e.NewValue;
var webBrowser = (WebBrowser)d;
webBrowser.NavigateToString(newValue);
}
}
XAML Binding WebBrowser to DependancyProperty:
<WebBrowser Grid.Column="2" HorizontalAlignment="Center" src:WebBrowserHelper.Body="{Binding HTMLBody}" VerticalAlignment="Center" Height="Auto" Width="Auto" />
ViewModel that bound to ItemsSource of Tab Control:
public class SomeVM : ViewModelBase, INotifyPropertyChanged
{
private string _htmlBody;
private SomeView _myView = new SomeView();
public SomeVM (string tabName)
{
TabName = tabName;
string contentsAsHTML = do_a_whole_bunch_of_stuff_to_generate_an_HTML_string();
HTMLBody = contentsAsHTML;
}
public string HTMLBody
{
get { return _htmlBody; }
set
{
if (_htmlBody != value)
{
_htmlBody = value;
RaisePropertyChanged("HTMLBody");
}
}
}
public SomeView View
{
get {return _myView;}
set { }
}
public string TabName { get; set; }
}
MainViewModel, Creating the Tab collection:
private ObservableCollection<SomeVM> _tabs;
public ObservableCollection<SomeVM> Tabs
{
get
{
if (_tabs== null)
{
_tabs= new ObservableCollection<SomeVM>();
_tabs.Add(new SomeVM("Tab 1"));
_tabs.Add(new SomeVM("Tab 2"));
_tabs.Add(new SomeVM("Tab 3"));
}
return _tabs;
}
}
MainWindow.xaml setting up the Tab Binding:
<TabControl ItemsSource="{Binding Tabs, Source={StaticResource vm}}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding TabName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding View}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
My problem is that "OnBodyChanged" is fired multiple times on ever tab change. The HTML takes a few seconds to load, and I would rather it only loads when the property is actually modified in the viewmodel.
EDIT
Here's the smallest sample project that recreates my problem.
Your problem is not relevant to attached properties or MVVM.
In fact, the real problem is that TabControl destroy and recreate its child every time you change the selected tab. That would explain why the handler is invoked more than once. The VisualTree only contains the selected Tab.
If you can try with another control, you will see there are no errors.
For solving this issue, I will redirect you to this post.

Silverlight MVVM Light Listbox item Click Event

I'm using the MVVM Light Toolkit with Silverlight.
On my UserControl I have a ListBox that displays a list of files. Each file has a delete image next to the file name. In the DataTemplate for the listbox I have an image (or can use a button) and a TextBlock.
So I want to capture using the event when the user will clicks on the image(or button with image) to remove the file from the list of files.
But I cannot seem to capture the event. Maybe this is due to having the SelectedItem Event on the listbox?
public class MainViewModel : ViewModelBase
{
#region Properties
public const string SelectedListBoxFilePropertyName = "SelectedUploadFile";
private UploadFile _selectedUploadFile = null;
public UploadFile SelectedUploadFile
{
get
{
return _selectedUploadFile;
}
set
{
if (_selectedUploadFile == value)
return;
_selectedUploadFile = value;
RaisePropertyChanged(SelectedListBoxFilePropertyName);
}
}
public const string UploadFilesPropertyName = "UploadFiles";
private ObservableCollection<UploadFile> _uploadFiles = new ObservableCollection<UploadFile>();
public ObservableCollection<UploadFile> UploadFiles
{
get
{
return _uploadFiles;
}
set
{
if (_uploadFiles == value)
return;
_uploadFiles = value;
RaisePropertyChanged(UploadFilesPropertyName);
}
}
#endregion
public static ICommand BrowseCommand { get; private set; }
public static ICommand DragDropFileCommand { get; private set; }
public static ICommand RemoveCommand { get; private set; }
#region Constructor
public MainViewModel()
{
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
UploadFiles = new UploadFileContainer().UploadFiles;
}
else
{
// Code runs "for real"
}
WireUpCommands();
}
#endregion
#region Event Handlers
private void OnBrowseFileCommand()
{
var dialog = new OpenFileDialog();
dialog.ShowDialog();
if (dialog.Files != null)
AddFiles(dialog.Files);
}
private void OnDropFileCommand(DragEventArgs e)
{
var files = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];
AddFiles(files);
}
private void OnRemoveFileCommand()
{
UploadFiles.Remove(_selectedUploadFile);
}
#endregion
#region Private Methods
private void WireUpCommands()
{
BrowseCommand = new RelayCommand(OnBrowseFileCommand);
DragDropFileCommand = new RelayCommand<DragEventArgs>(e => OnDropFileCommand(e));
RemoveCommand = new RelayCommand(OnRemoveFileCommand);
UploadCommand = new RelayCommand(OnClickUploadCommand);
}
#endregion
}
<ListBox Grid.Row="1" Height="214" HorizontalAlignment="Left" AllowDrop="True" Margin="6,26,0,0" Name="UploadFilesListBox" VerticalAlignment="Top" Width="415" ItemsSource="{Binding Path=UploadFiles}" SelectedItem="{Binding Path=SelectedListBoxFile, Mode=TwoWay}" ScrollViewer.VerticalScrollBarVisibility="Auto">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Drop">
<cmd:EventToCommand Command="{Binding DragDropFileCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.Background>
<ImageBrush ImageSource="/FileUploadApplication;component/Resources/dragdrophere.png" Stretch="None" />
</ListBox.Background>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveCommand}">
<Image Source="/FileUploadApplication;component/Resources/delete.png"/>
</Button>
<Image Source="/FileUploadApplication;component/Resources/delete.png">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding RemoveCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image> <TextBlock Text=" " />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Since your ItemsSource is UploadFiles it's probably sending the event to UploadFile and not the view model the user control is bound to.
Your button is the element of the ItemTemplate. you're binding the listbox ItemsSource to the ObservableCollection. Every Itemtemplate DataContext is no MainViewModel, but UploadFile, which has no RemoveCommand.
I was solving this by adding to every item the parent object using constructor. RemoveCommand was inside the item's ViewModel and insede the remove function i was calling the parent's method to delete the item.
Not sure if that's the best solution but it worked for me.

WPF: Binding Customized ListBox & List<T>: PropertyChanged is always null

i have customized ListBox declared in XAML:
<ListBox x:Name="uicMDSQonfServer">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Margin="0,5,0,5">
<CheckBox IsChecked="{Binding RelativeSource={TemplatedParent},
Path=Activated}" />
<ContentPresenter Content="{Binding RelativeSource={TemplatedParent},
Path=Content}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I need to dsiplay and interop with generic List, where T is:
public class QonfServer: QonfBase, INotifyPropertyChanged
{
private string ip;
private bool activated;
public string Ip {
get { return ip; }
}
public bool Activated
{
get { return activated; }
set
{
if (activated == value)
return;
activated = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Activated"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
QonfBase is pretty simple Base class:
public class QonfBase
{
private int id;
public int ID { get; set; }
}
When i turn Activated property programmatically, checkbox do not change state. Debug: PropertyChanged = null. Anybody know, what is incorrect?
One obvious problem meets the eye: TemplatedParent is for use with ControlTemplates. Since you're using a DataTemplate, this should work:
<CheckBox IsChecked="{Binding Activated}" />
<ContentPresenter Content="{Binding Content}"/>
I didn't notice any problems with the C#.

How to bind a ListBox to Properties in a Class?

I have one main outstanding issue, I know now how to databind to lists and individual items, but there is one more problem I have with this, I need to bind a listbox to some properties that are in a Class.
For Example I have two Properties that are bound in some XAML in a class called Display:
Public Property ShowEventStart() As Visibility
Public Property ShowEventEnd() As Visibility
I can use these in my XAML but I want them to be in a ListBox/Drop-down List, how can I have my properties show in this list and be able to change their values, does it have to be a List to be in a List?
I just want to be able to modify these properties from a Drop-down list to toggle the Visibility of the ShowEventStart and ShowEventEnd Property Values using the Checkboxes in the Drop-down List.
Plus this must be a Silverlight 3.0 solution, I cannot figure out how to have something that can be bound in the XAML which is not a list and then bound it as a list to modify these items!
I just need a list of Checkboxes which alter the values of the Class Properties such as ShowEventStart and ShowEventEnd, there are other properties but this will be a start.
You can create a PropertyWrapper class and in you window code behind expose a property that returns a List<PropertyWrapper> and bind your ListBox.ItemsSource to it.
public class PropertyWrapper
{
private readonly object target;
private readonly PropertyInfo property;
public PropertyWrapper(object target, PropertyInfo property)
{
this.target = target;
this.property = property;
}
public bool Value
{
get
{
return (bool) property.GetValue(target, null);
}
set
{
property.SetValue(target, value, null);
}
}
public PropertyInfo Property
{
get
{
return this.property;
}
}
}
your window code behind:
public partial class Window1 : Window, INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
properties = new List<PropertyWrapper>
{
new PropertyWrapper(this, typeof(Window1).GetProperty("A")),
new PropertyWrapper(this, typeof(Window1).GetProperty("B")),
};
this.DataContext = this;
}
private List<PropertyWrapper> properties;
public List<PropertyWrapper> Properties
{
get { return properties; }
}
private bool a;
private bool b = true;
public bool A
{
get
{
return a;
}
set
{
if (value != a)
{
a = value;
NotifyPropertyChange("A");
}
}
}
public bool B
{
get
{
return b;
}
set
{
if (value != b)
{
b = value;
NotifyPropertyChange("B");
}
}
}
protected void NotifyPropertyChange(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(
this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
your window markup:
<ListBox ItemsSource="{Binding Path=Properties}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Property.Name}"/>
<CheckBox IsChecked="{Binding Path=Value}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Hope this helps
I tried to mock up something similar. See how this works for you and let me know if I misunderstood the question.
From MainPage.xaml:
<StackPanel Orientation="Horizontal">
<ListBox SelectedItem="{Binding ShowControls, Mode=TwoWay}" x:Name="VisibilityList"/>
<Button Content="Test" Visibility="{Binding ShowControls}"/>
<CheckBox Content="Test 2" Visibility="{Binding ShowControls}"/>
</StackPanel>
The code behind MainPage.xaml.cs:
public partial class MainPage : UserControl
{
VisibilityData visibilityData = new VisibilityData();
public MainPage()
{
InitializeComponent();
VisibilityList.Items.Add(Visibility.Visible);
VisibilityList.Items.Add(Visibility.Collapsed);
this.DataContext = visibilityData;
}
}
And the data class:
public class VisibilityData : INotifyPropertyChanged
{
private Visibility showControls = Visibility.Visible;
public Visibility ShowControls
{
get { return showControls; }
set
{
showControls = value;
OnPropertyChanged("ShowControls");
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public event PropertyChangedEventHandler PropertyChanged;
}
When you run that code you should get a ListBox with the options Visible and Collapsed and when you choose an option you should see the visibility of the button and checkbox is changed to reflect your choice. Let me know if that's not what you were looking for.
After thinking about this the solution occured to me, which is to construct the List in XAML then using a Converter on the Checkboxes to convert their Boolean IsChecked to the Property Visibility Property in the Class.
You can create a list such as:
<ComboBox Canvas.Left="6" Canvas.Top="69" Width="274" Height="25" Name="Display">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Display.EventStart,Mode=TwoWay,Converter={StaticResource VisibilityConverter}}"/>
<TextBlock Text="Event Start"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Display.EventEnd,Mode=TwoWay,Converter={StaticResource VisibilityConverter}}"/>
<TextBlock Text="Event End"/>
</StackPanel>
</ComboBox>
I can then Bind to the Two Properties I want (this example from the actual application).

Resources