Selecting TabItem in TabControl from ViewModel - c#

I have been struggling with this for a day or so, can't figure out what I'm doing wrong here. I want to be able to select any tab in my observable collection of tabs, and I want my selection to be visible in the UI. I have tried SelectedIndex and SelectedItem. I can see that my Properties are set but my tabs are not selected, nothing happens in the UI. Here is my code:
MainWindow.xaml
<Window x:Class="WpfApplication5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:WpfApplication5"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<ViewModel xmlns="clr-namespace:WpfApplication5" />
</Window.DataContext>
<StackPanel>
<Button Content="Select Tab Index 0" Click="Button_Click_0"/>
<Button Content="Select Tab Index 1" Click="Button_Click_1"/>
<Label Content="{Binding SelectedIndex, UpdateSourceTrigger=PropertyChanged}" />
<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="{Binding SelectedIndex, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<uc:TabContent Content="{Binding Content}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click_0(object sender, RoutedEventArgs e)
{
var viewModel = (ViewModel)DataContext;
viewModel.SelectedIndex = 0;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
var viewModel = (ViewModel)DataContext;
viewModel.SelectedIndex = 1;
}
}
ViewModel.cs
class ViewModel
{
private int _selectedIndex = 0;
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Tab> _tabCollection = new ObservableCollection<Tab>();
public ViewModel()
{
Tabs.Add(new Tab { Header = "Tab1", Content = new WpfApplication5.TabContent() });
Tabs.Add(new Tab { Header = "Tab2", Content = new WpfApplication5.TabContent() });
}
public ObservableCollection<Tab> Tabs
{
get { return _tabCollection; }
}
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
NotifyPropertyChanged("SelectedIndex");
}
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Tab.cs
class Tab
{
public UserControl Content { get; set; }
public string Header { get; set; }
}
TabContent.xaml
<Grid>
<Label Content="Hello World!" />
</Grid>

Your ViewModel class doesn't implement the INotifyPropertyChanged interface:
class ViewModel : INotifyPropertyChanged
{
...
That's your issue.

Related

Is it possible to bind the foreground color of any textcontrol

So I've been trying to figure this out for 3 days and I just can't seem to find a solution.
This is what I am trying to achieve.
I have a simple WPF project with a RichTextBox in it.
What my application is doing is that it acts like a CMD.
What I want to do now is that I want to change the message it saved when I press enter, I want the previous message to change color.
Here is a GIF showing what it looks like
https://i.imgur.com/srszUKG.gifv
I tried binding the Foreground of the TextBox inside the DataTemplate but that just made it to where the text wouldnt even show up.
<DataTemplate>
<TextBlock Text="{Binding Path=.}" Foreground="White" Name="SavedBlocks" FontFamily="Consolas"/>
</DataTemplate>
So what are my options here, I essentially want to change the color of the text depending on how long the message is.
XAML
<Window x:Class="WpfApp1Eh.MainWindow"
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:local="clr-namespace:WpfApp1Eh"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ScrollViewer Name="Scroller" Margin="0" Background="Black">
<StackPanel>
<ItemsControl ItemsSource="{Binding ConsoleOutput, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}" Foreground="White" Name="SavedBlocks" FontFamily="Consolas"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBox Text="{Binding ConsoleInput, Mode=TwoWay}" Background="Black" Foreground="White" FontFamily="Consolas" Name="InputBlock" BorderBrush="{x:Null}" SelectionBrush="{x:Null}" />
</StackPanel>
</ScrollViewer>
</Grid>
</Window>
main.cs
public partial class MainWindow : Window
{
ConsoleContent dc = new ConsoleContent();
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded1;
DataContext = dc;
}
private void MainWindow_Loaded1(object sender, RoutedEventArgs e)
{
InputBlock.KeyDown += InputBlock_KeyDown;
InputBlock.Focus();
}
void InputBlock_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
dc.ConsoleInput = InputBlock.Text;
dc.RunCommand();
InputBlock.Focus();
Scroller.ScrollToBottom();
}
}
}
ConsoleContent.cs
public class ConsoleContent : INotifyPropertyChanged
{
string consoleInput = string.Empty;
ObservableCollection<string> consoleOutput = new ObservableCollection<string>() { "Console Emulation Sample..." };
public string ConsoleInput
{
get
{
return consoleInput;
}
set
{
consoleInput = value;
OnPropertyChanged("ConsoleInput");
}
}
public ObservableCollection<string> ConsoleOutput
{
get
{
return consoleOutput;
}
set
{
consoleOutput = value;
OnPropertyChanged("ConsoleOutput");
}
}
public void RunCommand()
{
ConsoleOutput.Add(ConsoleInput);
//myBrush = Brushes.Orange;
// do your stuff here.
ConsoleInput = String.Empty;
}
private System.Windows.Media.Brush _foregroundColor = System.Windows.Media.Brushes.DarkSeaGreen;
public System.Windows.Media.Brush ForegroundColor
{
get { return _foregroundColor; }
set
{
_foregroundColor = value;
OnPropertyChanged("ForegroundColor");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Change the type of consoleOutput from ObservableCollection<string> to ObservableCollection<YourType> where YourType is a class that represents a line of input with a text string and a Foreground Brush:
public class YourType : INotifyPropertyChanged
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; OnPropertyChanged("Text"); }
}
private Brush _foreground;
public Brush Foreground
{
get { return _foreground; }
set { _foreground = value; OnPropertyChanged("Foreground"); }
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Bind to the properties of this class in your XAML:
<ScrollViewer Name="Scroller" Margin="0" Background="Black">
<StackPanel>
<ItemsControl ItemsSource="{Binding ConsoleOutput, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"
Foreground="{Binding Foreground}"
Name="SavedBlocks" FontFamily="Consolas"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBox Text="{Binding ConsoleInput, Mode=TwoWay}" Background="Black" Foreground="White" FontFamily="Consolas" Name="InputBlock" BorderBrush="{x:Null}" SelectionBrush="{x:Null}" />
</StackPanel>
</ScrollViewer>
You can then set the Foreground property of each individual item in the source collection:
public void RunCommand()
{
ConsoleOutput.Add(new YourType { Text = ConsoleInput, Foreground = Brushes.Orange } );
ConsoleInput = String.Empty;
}

How to set dependency property of ItemsControl.ItemTemplate and handle its events

I have a main control (MainWindow.xaml) and an user control (ItemView.xaml). MainWindow contains an ItemsControl for all the ItemView-s and a simple button to add an item. All logic is (should be?) inside two corresponding viewmodels (MainWindowViewModel and ItemViewModel). Below is my code (made it as short as possible), but I have two problems with it:
When a new item is added it is correctly displayed but the exception is raised (Cannot create default converter to perform 'two-way' conversions between types 'WpfApplication1.ItemView' and 'WpfApplication1.ItemViewModel'.).
The OnDelete event handler in MainWindowViewModel is never raised? Edit: actually the ViewModel property inside BtnDeleteClick is null so yeah... of course.
Btw - I use Fody PropertyChanged.
MainWindow.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Grid.Row="0" Width="100" Height="35" Content="Add" HorizontalAlignment="Left" Margin="10" Click="BtnAddClick"></Button>
<Border Grid.Row="1" MinHeight="50">
<ItemsControl ItemsSource="{Binding ViewModel.Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<wpfApplication1:ItemView ViewModel="{Binding ., PresentationTraceSources.TraceLevel=High, Mode=TwoWay}"></wpfApplication1:ItemView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Grid>
</Window>
MainWindow.xaml.cs:
[ImplementPropertyChanged]
public partial class MainWindow
{
public MainWindowViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new MainWindowViewModel();
}
private void BtnAddClick(object sender, RoutedEventArgs e)
{
ViewModel.Add();
}
}
MainWindowViewModel.cs:
[ImplementPropertyChanged]
public class MainWindowViewModel
{
public ObservableCollection<ItemViewModel> Items { get; set; }
public MainWindowViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
}
public void Add()
{
var item = new ItemViewModel();
item.OnDelete += (sender, args) =>
{
Debug.WriteLine("-- WAITING FOR THIS TO HAPPEN --");
Items.Remove(item);
};
Items.Add(item);
}
}
ItemViewModel.xaml:
<UserControl x:Class="WpfApplication1.ItemView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Button Width="100" Height="35" Content="Delete" Click="BtnDeleteClick"></Button>
</Grid>
</UserControl>
ItemView.xaml.cs:
[ImplementPropertyChanged]
public partial class ItemView
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register
(
"ViewModel", typeof(ItemViewModel), typeof(ItemView), new UIPropertyMetadata(null)
);
public ItemViewModel ViewModel
{
get { return (ItemViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public ItemView()
{
InitializeComponent();
}
private void BtnDeleteClick(object sender, RoutedEventArgs e)
{
ViewModel.Delete();
}
}
And ItemViewModel.cs:
[ImplementPropertyChanged]
public class ItemViewModel
{
public event EventHandler OnDelete;
public void Delete()
{
var handler = OnDelete;
if (handler != null)
{
handler(this, new EventArgs());
}
}
}
You should not set
DataContext="{Binding RelativeSource={RelativeSource Self}}"
in the XAML of your ItemView. It effectively breaks the ViewModel="{Binding .}" binding in MainWindow.xaml, because the DataContext is no longer an ItemsViewModel, but an ItemsView.
As a rule, you should never explicitly set the DataContext of a UserControl, because all "external" bindings would then require an explicit Source or RelativeSource value.
That said, you're doing all this way too complicated. Instead of having a button click handler in your ItemsView, you could simply have a view model with a delete command, and bind the Button's Command property to this command.
It may look like this:
public class ItemViewModel
{
public string Name { get; set; }
public ICommand Delete { get; set; }
}
public class MainViewModel
{
public MainViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
}
public ObservableCollection<ItemViewModel> Items { get; private set; }
public void AddItem(string name)
{
Items.Add(new ItemViewModel
{
Name = name,
Delete = new DelegateCommand(p => Items.Remove(p as ItemViewModel))
});
}
}
and would be used like this:
<UserControl x:Class="WpfApplication1.ItemView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Content="Delete"
Command="{Binding Delete}"
CommandParameter="{Binding}"/>
</Grid>
</UserControl>

How can I get data from user Control in MainWindow

I have user control which has two controls
Label and TextBlock
<UserControl x:Class=MyClass....
d:DesignHeight="300" d:DesignWidth="300" x:Name="MyUsrCtrl">
<StackPanel>
<Label Content={Binding MyLabelContent} x:Name="MyLabel"...../>
<TextBlock Content={Binding MyTextBlockContent} x:Name="MyTextBlock"...../>
</StackPanel>
</UserControl>
and In my MainWindow I have a ListBox whose ItemSource is binded to collection of this usercontrol
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
<Grid>
<ListBox x:Name="myListBox" Grid.Row="0"
ItemsSource="{Binding Path=_myControl}"> // collection of user controls
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<local:MyUserControl x:Name="myUserControl" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
How can I get the value of the Textblock and Label when any item is selected in Listbox?
this worked for me:
Created a model called MyControl that "represents" the data in MyUserControl
Created an ObservableCollection That "represents" the data in the Listbox
This way you can also the delete all the x:Name
Seperates Data from UI
MainWindow.xaml
<ListBox x:Name="MyListBox" Grid.Row="0"
ItemsSource="{Binding MyControls}"
SelectionChanged="MyListBox_OnSelectionChanged"
SelectionMode="Single"
>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<local:MyUserControl></local:MyUserControl>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
MainWindow.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
MyControls = new ObservableCollection<MyControl>();
var a = new MyControl { MyLabelContent = "label content 1", MyTextBlockContent = "Text content 1" };
var b = new MyControl { MyLabelContent = "label content 2", MyTextBlockContent = "Text content 2" };
MyControls.Add(a);
MyControls.Add(b);
}
private void MyListBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = sender as ListBox;
if (listBox != null)
{
var selectedItem = listBox.SelectedItems[0] as MyControl;
var textBlockContent = selectedItem.MyTextBlockContent; //text in textblock
var labelContent = selectedItem.MyLabelContent; //text in label
}
}
private ObservableCollection<MyControl> _myControls;
public ObservableCollection<MyControl> MyControls
{
get { return _myControls; }
set
{
_myControls = value;
NotifyPropertyChanged("MyControls");
}
}
#region PropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
MyUserControl.XAML
<StackPanel>
<Label Content="{Binding MyLabelContent}" />
<TextBlock Text="{Binding MyTextBlockContent}" />
</StackPanel>
MyControl.cs
public class MyControl
{
public string MyLabelContent { get; set; }
public string MyTextBlockContent { get; set; }
}
Hopes this works for you :)
Here is a link to a working sample:
https://drive.google.com/file/d/0B8O-XH0V_o1hNXprX2c0S0xJUFU/view?usp=sharing
If you want to get TextBlock from selected item, you can do like this:
var selectedUserControl = myListBox.SelectedItem as MyUserControl;
TextBlock textBlock = selectedUserControl.MyTextBlock;
Hope helps!

Data Template not shown

A listbox data template doesn't show and I cannot figure out why.
If I don't use a DataTemplate and copy the contents into the control section itself, it's fine.
I don't do very much binding in XAML, I usually do it all in code. What did I do wrong?
XAML
<UserControl x:Class="Cis.CustomControls.CisArrivalsPanel"
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"
mc:Ignorable="d" Height="296" Width="876">
<UserControl.Resources>
<DataTemplate x:Key="DataTemplate">
<ListBoxItem>
<StackPanel>
<TextBlock Background="Blue" Text="{Binding Path=StationName}" />
<TextBlock Background="Brown" Text="{Binding Path=ArrivalPlatform}" />
</StackPanel>
</ListBoxItem>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<ListBox Width="487" Margin="0,66,0,33" ItemTemplate="{StaticResource DataTemplate}">
</ListBox>
</StackPanel>
</Grid>
</UserControl>
CS
public partial class CisArrivalsPanel : UserControl
{
public CisArrivalsPanel()
{
InitializeComponent();
this.DataContext = new ArrivalRowItem();
}
}
Model
public class ArrivalRowItem : INotifyPropertyChanged
{
public ArrivalRowItem()
{
this.StationName = "Lincoln";
this.ArrivalPlatform = "1";
}
private string _stationName;
public string StationName
{
get
{
return _stationName;
}
set
{
_stationName = value;
NotifyPropertyChanged("StationName");
}
}
private string _arrivalPlatform;
public string ArrivalPlatform
{
get
{
return _arrivalPlatform;
}
set
{
_arrivalPlatform = value;
NotifyPropertyChanged("ArrivalPlatform");
}
}
private DateTime _arrivalDateTime;
public DateTime ArrivalDateTime
{
get
{
return _arrivalDateTime;
}
set
{
_arrivalDateTime = value;
NotifyPropertyChanged("ArrivalDateTime");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
You have everything set up, but you don't actually have any data.
ListBox, like other ItemsControls acts against a collection of data, and generates an instance of the template for each item it finds.
Given that you haven't set ItemsSource or populated any collection I can see, you need to create a collection (probably an ObservableCollection) and set the ItemsSource to it via binding. Then add some items to it, and the ListBox will display them!

Communicating with ViewModel from MainView

I am new to MVVM and still trying to get a grasp on it so let me know if I'm setting this up wrong. What I have is a UserControl with a ListView in it. I populate this ListView with data from the ViewModel then add the control to my MainView. On my MainView I have a button that I want to use to add an item to the ListView. Here is what I have:
Model
public class Item
{
public string Name { get; set; }
public Item(string name)
{
Name = name;
}
}
ViewModel
public class ViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private ObservableCollection<Item> _itemCollection;
public ViewModel()
{
ItemCollection = new ObservableCollection<Item>()
{
new Item("One"),
new Item("Two"),
new Item("Three"),
new Item("Four"),
new Item("Five"),
new Item("Six"),
new Item("Seven")
};
}
public ObservableCollection<Item> ItemCollection
{
get
{
return _itemCollection;
}
set
{
_itemCollection = value;
OnPropertyChanged("ItemCollection");
}
}
}
View (XAML)
<UserControl.Resources>
<DataTemplate x:Key="ItemTemplate">
<StackPanel Orientation="Vertical">
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<UserControl.DataContext>
<local:ViewModel />
</UserControl.DataContext>
<Grid>
<ListView ItemTemplate="{StaticResource ItemTemplate}" ItemsSource="{Binding ItemCollection}">
</ListView>
</Grid>
MainWindow
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.mainContentControl.Content = new ListControl();
}
private void Button_Add(object sender, RoutedEventArgs e)
{
}
}
MainWindow (XAML)
<Grid>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Width="100" Height="30" Content="Add" Click="Button_Add" />
</StackPanel>
<ContentControl x:Name="mainContentControl" />
</DockPanel>
</Grid>
Now, from what I understand, I should be able to just an item to ItemCollection and it will be updated in the view. How do I do this from the Button_Add event?
Again, if I'm doing this all wrong let me know and point me in the right direction. Thanks
You should not interact directly with the controls.
What you need to do is define a Command (a class that implements the ICommand-interface) and define this command on your ViewModel.
Then you bind the Button's command property to this property of the ViewModel. In the ViewModel you can then execute the command and add an item directly to your list (and thus the listview will get updated through the automatic databinding).
This link should provide more information:
http://msdn.microsoft.com/en-us/library/gg405484(v=pandp.40).aspx#sec11

Categories

Resources