TabControl SelectedItem Binding Problems - wpf

In a Tabbed interface application i'm trying to pass the TabItem Header string or the object contained in the selected TabItem to the view model to use it to
publish an event like this:
In the View (xaml):
<TabControl x:Name="MyTC"
prism:RegionManager.RegionName="{x:Static inf:RegionNames.MainRegion}"
SelectedItem="{Binding Path=TabControlSelectedItem,UpdateSourceTrigger=PropertyChanged,Mode=Twoway}"
Cursor="Hand"
Grid.Row="0"
Grid.Column="1">
<TabControl.ItemTemplate>
<DataTemplate>
<!--DataContext="{Binding ElementName=MyTC, Path=SelectedItem}"-->
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Margin="3"
Text="{Binding Path=DataContext.DataContext.HeaderInfo, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabItem}}}"
/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding HeaderClickCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
In View Model
//***********************************************************
//Constructor
public ShellWindowViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
this.HeaderClickCommand = new DelegateCommand(OnHeaderClick);
}
//SelectedItem Binding
private object tabControlSelectedItem;
public object TabControlSelectedItem
{
get { return tabControlSelectedItem; }
set
{
if (tabControlSelectedItem != value)
{
tabControlSelectedItem = value;
OnPropertyChanged("TabControlSelectedItem");
}
}
}
//*****************************************************************************************************
//this handler publish the Payload "SelectedSubsystem" for whoever subscribe to this event
private void OnHeaderClick()
{
//EA for communication between Modules not within Modules
string TabHeader = (TabControlSelectedItem as TabItem).Header.ToString();
eventAggregator.GetEvent<SubsystemIDSelectedEvent>().Publish(TabHeader);
}
but there is something wrong because when i click the TabItem nothing happen and when i inserted a breakpoint # TabControlSelectedItem
property i found it contain the view namespace.i want the TabControlSelectedItem to get the selected Tab header string or the object in
the selected tab item.
Your help is very much appreciated.

I just tried something similar and it works fine.
Here is my xaml
<TabControl ItemsSource="{Binding Matches}"
SelectedItem="{Binding SelectedRecipe}">
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding Path=Date}" />
<TextBlock Grid.Column="1"
TextAlignment="Center"
Text="{Binding Path=Name}" />
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
And then in my viewmodel
public object SelectedRecipe
{
get
{
return _selectedRecipe;
}
set
{
_selectedRecipe = value;
OnNotifyPropertyChanged("SelectedRecipe");
}
}

Related

PropertyChanged value is null for child ViewModel property

I have an ObservableCollection of ViewModels in my main ViewModel. The binding seems to work fine since I can switch views. However, raising the ViewModelBase OnPropertyChanged method (which work for other stuff) in an element of the ObservableCollection result in a null PropertyChanged value in ViewModelBase.
Here's my main code snippets:
In my main ViewModel Constructor:
public EditorViewModel()
{
base.DisplayName = Strings.EditorName;
_availableEditors = new ObservableCollection<ViewModelBase>();
AvailableEditors.Add(new GBARomViewModel(646, 384));
AvailableEditors.Add(new MonsterViewModel(800, 500));
CurrentEditor = _availableEditors[0];
}
At GBA ROM loading, ViewModel and Model properties are updated:
void RequestOpenRom()
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.DefaultExt = ".gba";
dlg.Filter = "GBA ROM (.gba)|*.gba|All files (*.*)|*.*";
dlg.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
Nullable<bool> result = dlg.ShowDialog();
if (result == true)
{
if(CurrentEditor is GBARomViewModel)
{
(CurrentEditor as GBARomViewModel).ReadRom(dlg.FileName);
}
}
}
In my main View: Variation of TabControl (to have view switching and view states preservation).
<controls:TabControlEx ItemsSource="{Binding AvailableEditors}"
SelectedItem="{Binding CurrentEditor}"
Style="{StaticResource BlankTabControlTemplate}"
MinWidth="{Binding CurrentEditorWidth}"
MinHeight="{Binding CurrentEditorHeight}"
MaxWidth="{Binding CurrentEditorWidth}"
MaxHeight="{Binding CurrentEditorHeight}"
Width="{Binding CurrentEditorWidth}"
Height="{Binding CurrentEditorHeight}"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<controls:TabControlEx.Resources>
<DataTemplate DataType="{x:Type vm:GBARomViewModel}">
<vw:GBARomView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MonsterViewModel}">
<vw:MonsterView />
</DataTemplate>
</controls:TabControlEx.Resources>
</controls:TabControlEx>
In GBARomViewModel (child ViewModel, element of AvailableEditors)
public String CRC32
{
get
{
return _rom.CRC32;
}
set
{
if (value.Equals(_rom.CRC32))
{
return;
}
_rom.CRC32 = value;
OnPropertyChanged("CRC32");
}
}
Property binding in child View
Now this is a UserControl so I'll put its code as well after. Other properties at startup work such as LabelWidth and the LabelValue. Giving a default value to TextBoxValue in XAML also work.
<StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10, 0, 0, 10" Width="300">
<dlb:DefaultLabelBox LabelWidth="82" TextBoxWidth="100" HorizontalAlignment="Left" LabelValue="{x:Static p:Strings.RomTitle}" TextBoxValue="{Binding Title}" />
<dlb:DefaultLabelBox LabelWidth="82" TextBoxWidth="100" HorizontalAlignment="Left" LabelValue="{x:Static p:Strings.RomGameCode}" TextBoxValue="{Binding GameCode}" />
<dlb:DefaultLabelBox LabelWidth="82" TextBoxWidth="100" HorizontalAlignment="Left" LabelValue="{x:Static p:Strings.RomRomSize}" TextBoxValue="{Binding RomSize}" />
<dlb:DefaultLabelBox LabelWidth="82" TextBoxWidth="100" HorizontalAlignment="Left" LabelValue="{x:Static p:Strings.RomCRC32}" TextBoxValue="{Binding CRC32}" />
<dlb:DefaultLabelBox LabelWidth="82" TextBoxWidth="200" HorizontalAlignment="Left" LabelValue="{x:Static p:Strings.RomMD5Checksum}" TextBoxValue="{Binding MD5Checksum}"/>
</StackPanel>
DefaultLabelBox.cs
<UserControl x:Name="uc">
<StackPanel>
<TextBlock Text="{Binding Path=LabelValue, ElementName=uc}"
Width="{Binding Path=LabelWidth, ElementName=uc}"/>
<Label Content="{Binding Path=TextBoxValue, Mode=OneWay, ElementName=uc}"
Width="{Binding Path=TextBoxWidth, ElementName=uc}"/>
</StackPanel>
</UserControl>
DefaultLabelBox.xaml.cs
public string TextBoxValue
{
get {
return (string)GetValue(TextBoxValueProperty);
}
set {
SetValue(TextBoxValueProperty, value);
}
}
public static readonly DependencyProperty TextBoxValueProperty =
DependencyProperty.Register("TextBoxValue", typeof(string), typeof(DefaultLabelBox), new PropertyMetadata(default(string)));
Control Template
<Style TargetType="dlb:DefaultLabelBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="dlb:DefaultLabelBox">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding LabelValue, RelativeSource={RelativeSource TemplatedParent}}"
MinWidth="20"
Width="{Binding LabelWidth, RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"
FontFamily="Mangal"
Height="20"
FontSize="13"/>
<Label Content="{Binding TextBoxValue, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
BorderBrush="{StaticResource DefaultLabelBoxBorderBrush}"
BorderThickness="1"
Padding="1,1,1,1"
Background="{StaticResource DefaultLabelBoxBackgroundBrush}"
Foreground="{StaticResource DefaultLabelBoxForeground}"
MinWidth="60"
Height="20"
VerticalAlignment="Center"
FontFamily="Mangal"
Width="{Binding TextBoxWidth, RelativeSource={RelativeSource TemplatedParent}}"
FontSize="13"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I tried a few things but being new to MVVM I don't know if I have a DataContext issue of a binding one. Any help would be appreciated.
Edit: I made changes to some code to illustrate the working solution for me as well as adding the ControlTemplate I had forgot. I'm not sure if Mode=OneWay is mandatory in UserControl and ControlTemplate but it's working now so I'm leaving it as it is.
In order to make a binding like
<dlb:DefaultLabelBox ... TextBoxValue="{Binding CRC32, Mode=TwoWay}" />
work, the DefaultLabelBox needs to inherit its DataContext from its parent control (this is btw. the reason why a UserControl should never explicitly set its DataContext).
However, the "internal" bindings in the UserControl's XAML then need an explicitly specified Source or RelativeSource or ElementName.
So they should (for example) look like this:
<UserControl ... x:Name="uc">
<StackPanel>
<TextBlock
Text="{Binding Path=LabelValue, ElementName=uc}"
Width="{Binding Path=LabelWidth, ElementName=uc}"/>
<TextBox
Text="{Binding Path=TextBoxValue, Mode=TwoWay, ElementName=uc}"
Width="{Binding Path=TextBoxWidth, ElementName=uc}"/>
</StackPanel>
</UserControl>

Set Grid background color programmatically in Silverlight

I have a Data template
<DataTemplate x:Key="ConnectorItemFactory">
<Button Style="{StaticResource TextOnlyActionTileButtonStyle}" Margin="0,0,5,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Content="{Binding}"
ContentTemplate="{StaticResource TileTemplate}"
Command="{StaticResource NavigationCommand}"
CommandParameter="{Binding}"
Height="156"
Width="156"
>
<i:Interaction.Behaviors>
<UICommon:XboxBehavior VuiBinding="{Binding VuiTitle}"/>
</i:Interaction.Behaviors>
</Button>
</DataTemplate>
as a ContentTemplate it is used another data template
<DataTemplate x:Key="TileTemplate">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="{StaticResource ActionButtonBackgroundBrush}">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="7,7,7,4">
<Controls:TrimmedTextBlock Text="{Binding Title}" Style="{StaticResource TextOnlyTileTitleStyle}" TextWrapping="Wrap" />
<Controls:TrimmedTextBlock Text="{Binding Converter={StaticResource SubtitleTextFormatter}}" Style="{StaticResource TextOnlyTileSubtitleStyle}"/>
</StackPanel>
</Grid>
</DataTemplate>
I'm using a TemplateFactory to load the content template
public class GridTemplateFactory : ModelViewTemplateFactory
{
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
var dataTemplate = Application.Current.Resources["ConnectorItemFactory"] as DataTemplate;
// var grid = ((Button)dataTemplate.LoadContent()).ContentTemplate.LoadContent();
// ((Grid)grid).Background = new SolidColorBrush(Colors.Orange);
// ((Button)dataTemplate.LoadContent()).ContentTemplate.LoadContent().SetValue(Grid.BackgroundProperty, new SolidColorBrush(Colors.Orange));
this.ContentTemplate = dataTemplate;
}
}
In the OnContentChanged method I want to change the Grid background color from the TileTemplate data template.
I tried to do it like in commented code above, but it doesn't work. How can I change the color here?

Custom templated combobox with special non templated item

I have a RadTreeView, in each item there is a RadCombobox with some elements. Now I need to add some "special" item into each combobox. User can click on this item to add new element in combobox:
My current code:
<DataTemplate x:Key="Monitor">
<Grid Height="Auto" Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Height="16" Width="16" Source="icons\monitor.png" />
<TextBlock Text="{Binding Name}" Margin="5 0 0 0" Grid.Column="1" Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
<!-- PROBLEM IS HERE -->
<telerik:RadComboBox Name="RadComboSchedule"
Grid.Column="2"
Margin="10 0 0 0"
Width="155"
ItemsSource="{Binding Source={StaticResource DataSource}, Path=ScheduleDataSource}"
ItemTemplate="{StaticResource ComboBoxTemplate}"
>
</telerik:RadComboBox>
<Button Name="BtnRemoveMonitor" Grid.Column="3" Style="{StaticResource ButtonListBoxItemStyle}" Template="{StaticResource RemoveButtonTemplate}" />
</Grid>
</DataTemplate>
<HierarchicalDataTemplate x:Key="Group"
ItemTemplate="{StaticResource Monitor}"
ItemsSource="{Binding Monitors}">
<TextBlock Text="{Binding Name}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</HierarchicalDataTemplate>
<telerik:RadTreeView
Name="RadTreeViewGroups"
Height="auto"
Width="auto"
ItemsSource="{Binding Source={StaticResource DataSource}, Path=GroupsDataSource}"
ItemTemplate="{StaticResource Group}"
>
</telerik:RadTreeView>
So, I have all like at a screenshot without element "Add new item".
Any ideas?
PS It's not a problem to use standard WPF Combobox and TreeView controls.
You can create a new item in the DataSource of the ComboBox which name is "ADD NEW ITEM" and handle when the user select it.
private void SelectItem(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems[0].ToString() == "new")
{
string newItem = "completely new item";
dataSource.Add(newItem);
((ComboBox)sender).SelectedItem = newItem;
}
}
In this question you can see a better example that each item is an instance of a class, so it's easier to handle the "add item" request:
Databound WPF ComboBox with 'New...' item
Edit (about the 'add item' button template):
Based on the example above
Having this class
public class DisplayClass
{
public string Name { get; set; }
public bool IsDummy { get; set; }
}
You bind ComboBox.ItemsSource to an ObservableCollection like this one:
public ObservableCollection<DisplayClass> DataSource { get; set; }
Add that "dummy" item to the collection
DataSource.Add(new DisplayClass { Name = "ADD ITEM", IsDummy = true });
Then you handle the item selection with something like this:
private void SelectItem(object sender, SelectionChangedEventArgs e)
{
var comboBox = (ComboBox)sender;
var selectedItem = comboBox.SelectedItem as DisplayClass;
if (selectedItem != null && selectedItem.IsDummy)
{
//Creating the new item
var newItem = new DisplayClass { Name = comboBox.Items.Count.ToString(), IsDummy = false };
//Adding to the datasource
DataSource.Add(newItem);
//Removing and adding the dummy item from the collection, thus it is always the last on the 'list'
DataSource.Remove(selectedItem);
DataSource.Add(selectedItem);
//Select the new item
comboBox.SelectedItem = newItem;
}
}
To display the items properly, you'll need to change the ComboBox.ItemTemplate, making the image invisible when the item is dummy
<ComboBox ItemsSource="{Binding DataSource}" SelectionChanged="SelectItem">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Width="180" />
<Image HorizontalAlignment="Right" Source="..." MouseLeftButtonUp="DeleteItem">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding IsDummy}" Value="True">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Selectionchanged event firing for every row

I am using a cascading comboboxes inside datagrid.I am able to get the datas based on selectionchanged but that event is firing for every row.
Here is my code:
<sdk:datagridtemplatecolumn header="Category" width="110">
<sdk:datagridtemplatecolumn.celltemplate>
<datatemplate>
<combobox foreground="Black" height="30" isenabled="{Binding Source={StaticResource EffortViewModel}, Path=ComboBoxStatus}" itemssource="{Binding Source={StaticResource EffortViewModel},Path=ProjTypeTaskCtry}" displaymemberpath="TaskCtgyName" selectedvaluepath="TaskCtgy_FK" selectedvalue="{Binding Source={StaticResource EffortViewModel}, Path=TaskCtgy_FKField,Mode=TwoWay}" />
</datatemplate>
</sdk:datagridtemplatecolumn.celltemplate>
</sdk:datagridtemplatecolumn>
<sdk:datagridtemplatecolumn header="SubCategory" width="110">
<sdk:datagridtemplatecolumn.celltemplate>
<datatemplate>
<combobox foreground="Black" height="30" isenabled="{Binding Source={StaticResource EffortViewModel}, Path=ComboBoxStatus}" itemssource="{Binding Source={StaticResource EffortViewModel},Path=SubCtry,Mode=OneWay}" displaymemberpath="TaskSubCtgyName" selectedvaluepath="{Binding TaskSubCtgy_PK, Mode=TwoWay}" selectedvalue="{Binding TaskSubCtgy_FKField,Mode=OneTime}" selectedindex="{Binding TaskSubCtgy_FKField}" />
</datatemplate>
</sdk:datagridtemplatecolumn.celltemplate>
</sdk:datagridtemplatecolumn>
I had the same problem in Silverlight MVVM. I found a solution for this from somewhere. Hope this will help you.
namespace Test
{
public class ComboBoxSelectionChange : TriggerAction<DependencyObject>
{
public ComboBoxSelectionChange()
{
}
public ComboBox DayComboBox
{
get { return (ComboBox)GetValue(DayComboBoxProperty); }
set { SetValue(DayComboBoxProperty, value); }
}
public static readonly DependencyProperty DayComboBoxProperty =
DependencyProperty.Register("DayComboBox",
typeof(ComboBox),
typeof(ComboBoxSelectionChange),
new PropertyMetadata(null, OnDayComboBoxPropertyChanged));
private static void OnDayComboBoxPropertyChanged(DependencyObjectd, DependencyPropertyChangedEventArgs e)
{
var source = d as ComboBoxSelectionChange;
if (source != null)
{
var value = (ComboBox)e.NewValue;
}
}
protected override void Invoke(object o)
{
if (this.DayComboBox != null)
{
//this method will execute when the selection is changed
}
}
}
}
Use the Test namespace in Usercontrol assembly
xmlns:Common="clr-namespace:Test"
<UserControl.Resources>
<Common:ComboBoxSelectionChange x:Name="ComboBoxItem"/>
</UserControl.Resources>
<DataTemplate x:Key="EditMondayDataTemplate">
<ComboBox x:Name="cmbMonday" Height="26" Margin="3" ItemsSource="{Binding Monday,Mode=OneTime}" DisplayMemberPath="displayText" SelectedItem="{Binding Path=MonSelected,Mode=TwoWay}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="80">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<Common:ComboBoxSelectionChange DayComboBox="{Binding ElementName=cmbMonday}" TextParam="Monday"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>

ObservableCollection Images in Listbox to Content Control master detail WPf

I have an observablecollection of Images that get populated via the following code:
<StackPanel Orientation="Horizontal" Grid.Column="0">
<ListBox ItemsSource="{Binding BigImageView}" IsSynchronizedWithCurrentItem="True"
SelectedIndex="0" SelectedItem="{Binding CurrentItem}" />
</StackPanel>
<ContentControl Name="Detail" Content="{Binding BigImageView, Mode=OneWay}"
Margin="9,0,0,0" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top"/>
However the Content Control is supposed to bind to the BigImageView via an ObservableCollection
BigImage = new ObservableCollection<Image>();
_listView = CollectionViewSource.GetDefaultView(BigImage);
_listView.CurrentChanged += new EventHandler(OnCurrentChanged);
public System.ComponentModel.ICollectionView BigImageView
{
get
{
return _listView;
}
set
{
_listView = value;
OnPropertyChanged("BigImageView");
}
}
I want to return the image to the content control when I move the listbox. I have been racking my brain and trying everyhitn but it does not work. any help would be appreciated.
There is no need to bind the selecteditem, the collectionview should take care of that.
Try this:
<ListBox ItemsSource="{Binding BigImageView}" IsSynchronizedWithCurrentItem="True" />
<ContentControl Name="Detail" Content="{Binding BigImageView, Mode=OneWay}" VerticalAlignment="Top">
<ContentControl.ContentTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
<ContentControl.ContentTemplate>
1
Create a viewmodel with a list and a selected item:
public class BigImageViewModel : INotifyPropertyChanged
{
private string bigImage;
//string for path?
public ObservableCollection<string> BigImageView {get; set; } //Of course, make sure it has a value
public string SelectedBigImage
{
get { return bigImage; }
set { bigImage = values; NotifyPropertyChanged("SelectedBigImage"); }
}
}
Set this object on the DataContext of your control in the constructor:
DataContext = new BigImage(); //Make sure you initialize your list
Set the ListBox ItemsSource to your BigImage list, bind your SelectedItem to BigImageView
and use that in your content control:
<ListBox ItemsSource="{Binding BigImageView}" SelectedItem={Binding SelectedBigImage} />
ContentControl:
<ContentControl Name="Detail" Content="{Binding SelectedBigImage, Mode=OneWay}" VerticalAlignment="Top">
<ContentControl.ContentTemplate>
<DataTemplate>
<Image Source="{Binding}"/> <!-- Nice template for showing your string BigImage -->
</DataTemplate>
<ContentControl.ContentTemplate>
</ContentControl>
2
Or screw that view model:
Set the list directly in the constructor (after the InitializeComponent() ):
myListBox.ItemsSource = ObservableCollection<string>(); //Make sure you initialize your list with whatever your object is..
Give the list a name:
And bind with an ElementName binding to your selected item:
<ContentControl Name="Detail" Content="{Binding ElementName=myListBox, Path=SelectedItem}" VerticalAlignment="Top">
<ContentControl.ContentTemplate>
<DataTemplate>
<Image Source="{Binding}"/> <!-- Nice template for showing your string BigImage -->
</DataTemplate>
<ContentControl.ContentTemplate>
</ContentControl>

Resources