I'm trying to add buttons to UserControl. I have to follow MVVM pattern. I have created a class, DeviceButton, with different set/gets and a constructor. In a viewmodel, used as datacontext, is the ObservableCollection and a get-method for the collection. I have bound the collection to a ItemsControl source and tried to add a template. I guess I'm missing something 'cause the buttons won't load.
The UserControl is added into a tab (using dragablz) which as well is a part of a ObservableCollection, also added at run time (this is working just fine). The idea is, that the tab has a list of buttons that has to be created at run time, where the list is fetched from a web service - so the buttons has to be added dynamically/programatically. Overview is just the first tab - a template for each tab (reflecting the fetched items) is being implemented when the buttons work. For now, I'm just adding a test button to the collection, but as stated, this won't show. What am I missing?
I have a Overview.xaml file:
<UserControl // attributes omitted to save spaces... ask if needed>
<StackPanel>
<Border>
<TextBlock FontSize="16" FontWeight="Bold" TextWrapping="WrapWithOverflow"
TextAlignment="Center" HorizontalAlignment="Center"
Foreground="{DynamicResource AccentColorBrush}">
Welcome to Greenhouse App
</TextBlock>
</Border>
<ItemsControl ItemsSource="{Binding DeviceButtons}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vmodel:DeviceButton}">
<Button Width="50" Height="50" Margin="10 10 0 0" Command="{Binding OpenTab}"
CommandParameter="{Binding DeviceType}" HorizontalAlignment="Left"
VerticalAlignment="Top" Style="{DynamicResource SquareButtonStyle}">
<StackPanel>
<Image Source="{Binding ImageUrl}" />
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
// Manually added test button...
<Button x:Name="windMill" HorizontalAlignment="Left" Margin="10,0,0,0"
VerticalAlignment="Top" Width="50" Height="50"
Command="{Binding OpenTab}" FontFamily="Segoe Ui"
Style="{DynamicResource SquareButtonStyle}">
<StackPanel>
<Image Source="/Greenhouse;component/Icons/Windmill.png" />
</StackPanel>
</Button
</StackPanel>
I'm trying to add an ObservableCollection of type DeviceButton, the _deviceButtons collection (Binded as ItemsSource to the ItemsControl)
The tabList items are working just find, and I can manually add more if needed (these will later be added through the OpenNewTab-command, which should be bound to the buttons)
The DeviceButton file:
public class DeviceButton
{
private readonly string _content;
private readonly string _deviceType;
private readonly string _imageUrl;
public DeviceButton(string content, string deviceType, string imageUrl)
{
_content = content;
_deviceType = deviceType;
_imageUrl = imageUrl;
}
public string Content
{
get { return _content; }
}
public string DeviceType
{
get { return _deviceType; }
}
public string ImageUrl
{
get { return _imageUrl; }
}
}
The collection is located in a viewmodel file, MainWindowViewModel.cs:
public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged
{
public ICommand OpenTab { get; set; }
private string tabControl { get; set; } = "0";
private IInterTabClient _interTabClient;
private ObservableCollection<TabContent> _tabContents = new ObservableCollection<TabContent>();
private ObservableCollection<DeviceButton> _deviceButtons = new ObservableCollection<DeviceButton>();
public MainWindowViewModel()
{
_interTabClient = new MyInterTabClient();
DeviceButtons.Add(new DeviceButton("Windturbine", "windturbine", "/Greenhouse;component/Icons/Windmill.png"));
TabContents.Add(new TabContent("Overview", new Overview()));
OpenTab = new RelayCommand<object>(OpenNewTab);
}
private void OpenNewTab(object obj)
{
MessageBox.Show("Yo"); // Not yet implemented
RaisePropertyChanged(() => tabControl);
}
public ObservableCollection<TabContent> TabContents
{
get { return _tabContents; }
}
public ObservableCollection<DeviceButton> DeviceButtons
{
get { return _deviceButtons; }
}
public IInterTabClient InterTabClient
{
get { return _interTabClient; }
}
}
The buttons aren't loaded when I start the program (only the "test" button added manually in the WPF).
The UserControl, Overview, is considered a tab in another Controls.Metrowindow, MainWindow.xaml:
<Window.Resources>
<Style TargetType="{x:Type dragablz:TabablzControl}">
<Setter Property="CustomHeaderItemTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type viewmodel:TabContent}">
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type viewmodel:TabContent}">
<ContentPresenter Margin="4" Content="{Binding Content}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<dragablz:TabablzControl SelectedIndex="{Binding tabControl}" ItemsSource="{Binding TabContents}" x:Name="InitialTabablzControl" Margin="4 0 4 4">
<dragablz:TabablzControl.InterTabController>
<dragablz:InterTabController InterTabClient="{Binding MyInterTabClient}" />
</dragablz:TabablzControl.InterTabController>
</dragablz:TabablzControl>
I guess it's an issue with the resources/binding in the Overview.xaml, but I've exhausted all suggested solutions I could find.
Issue was in binding
ItemsSource="{Binding Path=DataContext.TabContents,
RelativeSource={RelativeSource AncestorLevel=1,
AncestorType={x:Type Window}, Mode=FindAncestor}}"
I suspect the DataContext is being set to a TabContent object, which does not have a DeviceButtons property
I would suggest using a tool like Snoop to verify your DataContext is what you expect.
Related
I have two problems, one small and one big, and I need help;
First, this is my code :
<ComboBox Name="cmb1" Width="165" Height="25" Margin="25,5,10,10"
ItemsSource="{Binding ChoicesList}"
SelectedItem="{Binding SelectedChoiceList}">
</ComboBox>
<ComboBox Name="cmb2" Width="165" Height="25" Margin="10,5,25,10">
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Sections}"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedChoiceList}" Value="Contract">
<Setter Property="ItemsSource" Value="{Binding Contract}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedChoiceList}" Value="Service">
<Setter Property="ItemsSource" Value="{Binding Services}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding Name}"
Margin="4,0"/>
<TextBlock Grid.Column="0"
Text="{Binding label}"
Margin="0"/>
<TextBlock Grid.Column="0"
Text="{Binding Start, StringFormat=d}"
Margin="0"/>
<TextBlock Grid.Column="1"
Text="{Binding End,StringFormat=d}"
Margin="0"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Another day, another problem!
In cmb2, I would like nothing to be loaded until I have made one of the three choices, is there a way?
(for the moment I load "section" list if nothing is chosen, and the binding changes according to the choices. I made this choice because the value of "section" can change, it can be "A" or "B" or "C" and more, so I would like "service" = service list, "contract" = contract list and "section"= all other values)
More importantly, I would like to rework this code because currently the DataTemplate is the same for all three binds, which was not awkward (If I choose "section", textblock displays "name" selected, cause section have no label, for example) but I would like, for contracts, to display "name" + "from" + "Starting date" + "To" + "Ending date".
If I add textblock "text = from" and "text = to", it will apply to all my choices, and I would have, for example, if I choose "section", a result like "nameOfSection + from + to", and I don't want that.
So I would like the textblock "from" and "to" to appear only for the choice "contract", I don't know if it's possible?
Hoping to have been clear, and thanking you for your time for the help you will give me;
Welcome to SO!
To answer your first point by removing the line that is setting the ItemSource you should be able to have nothing loaded
<Setter Property="ItemsSource" Value="{Binding Sections}"></Setter> can be removed and replaced with another DataTrigger.
To answer your second point you can use DataTemplates with the DataType property to apply a style to a specific data type. This means you can setup each of your, Service, Section and Contract to look differently / template their own properties.
<DataTemplate DataType="{x:Type classes:Contract}">
<Label Content="Contract"/>
</DataTemplate>
<DataTemplate DataType="{x:Type classes:Service}">
<Label Content="Service"/>
</DataTemplate>
<DataTemplate DataType="{x:Type classes:Section}">
<Label Content="Section"/>
</DataTemplate>
This bit of code demonstrates that.
Here is also a sample application I have made that should do what you've asked.
View
<Window x:Class="SOHelp.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:classes="clr-namespace:SOHelp.Classes"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type classes:Contract}">
<Label Content="Contract"/>
</DataTemplate>
<DataTemplate DataType="{x:Type classes:Service}">
<Label Content="Service"/>
</DataTemplate>
<DataTemplate DataType="{x:Type classes:Section}">
<Label Content="Section"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" Width="165" Height="25"
ItemsSource="{Binding ChoicesList}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedChoiceList}"/>
<ComboBox Grid.Row="1" Width="165" Height="25"
ItemsSource="{Binding SelectedChoiceList.Items}"/>
</Grid>
</Window>
Code behind / could be ViewModel
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Option> _choicesList;
private Option _selectedChoiceList;
public Option SelectedChoiceList
{
get { return _selectedChoiceList; }
set { _selectedChoiceList = value; NotifyPropertyChanged(); }
}
public ObservableCollection<Option> ChoicesList
{
get { return _choicesList; }
set { _choicesList = value; NotifyPropertyChanged(); }
}
public MainWindow()
{
InitializeComponent();
ChoicesList = new ObservableCollection<Option>
{
new Option("Contract", new ObservableCollection<object>
{
new Contract(), new Contract(), new Contract()
}),
new Option("Service", new ObservableCollection<object>
{
new Service(), new Service(), new Service()
}),
new Option("Section", new ObservableCollection<object>
{
new Section(), new Section(), new Section()
}),
};
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Classes I created
public class Option
{
public string Name { get; set; }
public ObservableCollection<object> Items { get; set; }
public Option(string name, ObservableCollection<object> items)
{
Name = name;
Items = items;
}
}
public class Service
{
}
public class Section
{
}
public class Contract
{
}
Hope some of this helps. Let me know if you have any questions.
I'm trying to use Caliburn.Micro to bind a view model of a nested ListBox but I'm stuck.
I have a list workspace that is divided in two sections a list of groups containtaining items in a different part of that workspace I want to show the details of either a group or an item in a group depending on what is the SelectedItem. I'm new to Caliburn.Micro and looked at the documentation and samples but don't know how to connect the dots. Specifically I'm trying to model this after the Caliburn.Micro.HelloScreens sample. The code I have so far:
The ViewModel:
public class AnalyzerGroupWorkspaceViewModel : Conductor<AnalyzerGroupWorkspaceViewModel>, IWorkspace
{
private Selected selected = Selected.AnalyzerGroup;
private const string name = "Analyzers";
public AnalyzerGroupWorkspaceViewModel(
IMappingEngine fromMapper,
IRepository<Model.AnalyzerGroup> analyzerGroups)
{
AnalyzerGroups = new ObservableCollection<IAnalyzerGroupViewModel>(analyzerGroups.GetAll().Select(fromMapper.Map<Model.AnalyzerGroup,AnalyzerGroupViewModel>));
}
public ObservableCollection<IAnalyzerGroupViewModel> AnalyzerGroups { get; private set; }
public string Name { get { return name; } }
public Selected Selected
{
get { return selected; }
set
{
if (value == selected) return;
selected = value;
NotifyOfPropertyChange(() => Selected);
}
}
private IConductor Conductor { get { return (IConductor) Parent; } }
public void Show()
{
var haveActive = Parent as IHaveActiveItem;
if (haveActive != null && haveActive.ActiveItem == this)
{
DisplayName = name;
Selected = Selected.AnalyzerGroup;
}
else
{
Conductor.ActivateItem(this);
}
}
}
The view:
<UserControl x:Class="Philips.HHDx.SSW.AnalyzerGroup.AnalyzerGroupWorkspaceView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<DockPanel>
<GroupBox Header="AnalyzerGroups" DockPanel.Dock="Top">
<ListBox x:Name="AnalyzerGroups">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" />
<ListBox x:Name="Analyzers">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id }"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
<GroupBox Header="Details">
<ContentControl cal:View.Context="{Binding Selected, Mode=TwoWay}"
cal:View.Model="{Binding}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"/>
</GroupBox>
</DockPanel>
</UserControl>
Next to that I have two UserControls that display the detailsof a group or item.
My specific question is how can I use the SelectedItem property of the two ListBoxes to modify the Selected property to switch between displaying the AnalyzerGroup details and the Analyzer details?
I've found the solution to the above described problem the solution consists of four parts:
Add a IsSelected property (that notifies changes) to both 'child' ViewModels
Bind the IsSelected property of the ListBox.ItemContainerStyle to the IsSelected property of the respective ViewModels
Attach a Caliburn.Micro Message to the 'outer' ListBox and use the $eventArgs argument
In the ViewModel bound to the entire UserControl implement the method corresponding to the Message and use the AddedItems property of the eventArgs to set the SelectedViewModel property setting the IsSelected property of the previous SelectedViewModel to false
The code then becomes:
The ViewModel:
public class AnalyzerGroupWorkspaceViewModel : PropertyChangedBase, IAnalyzerGroupWorkspaceViewModel
{
private IAnalyzerViewModel selectedViewModel;
private const string WorkspaceName = "Analyzers";
public AnalyzerGroupWorkspaceViewModel(
IMappingEngine fromMapper,
IRepository<Model.AnalyzerGroup> analyzerGroups)
{
AnalyzerGroups = new ObservableCollection<IAnalyzerGroupViewModel>(
analyzerGroups.GetAll().Select(
fromMapper.Map<Model.AnalyzerGroup, AnalyzerGroupViewModel>));
}
public void SelectionChanged(object eventArgs)
{
var typedEventArgs = eventArgs as SelectionChangedEventArgs;
if (typedEventArgs != null)
{
if (typedEventArgs.AddedItems.Count > 0)
{
var item = typedEventArgs.AddedItems[0];
var itemAsGroup = item as IAnalyzerViewModel;
if (itemAsGroup != null)
{
SelectedViewModel = itemAsGroup;
}
}
}
}
public ObservableCollection<IAnalyzerGroupViewModel> AnalyzerGroups { get; private set; }
public string Name { get { return WorkspaceName; } }
public IAnalyzerViewModel SelectedViewModel
{
get { return selectedViewModel; }
set
{
if (Equals(value, selectedViewModel))
{
return;
}
if (SelectedViewModel != null)
{
SelectedViewModel.IsSelected = false;
}
selectedViewModel = value;
NotifyOfPropertyChange(() => SelectedViewModel);
}
}
}
The View:
<UserControl x:Class="Philips.HHDx.SSW.AnalyzerGroup.AnalyzerGroupWorkspaceView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<DockPanel>
<GroupBox Header="AnalyzerGroups" DockPanel.Dock="Top">
<ListBox SelectionMode="Single"
x:Name="AnalyzerGroups"
cal:Message.Attach="[Event SelectionChanged] = [Action SelectionChanged($eventArgs)]">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" BorderBrush="DarkGray">
<StackPanel Orientation="Vertical" Margin="10">
<TextBlock Text="{Binding Name}" />
<ListBox SelectionMode="Single" ItemsSource="{Binding Analyzers}" >
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="DarkGray">
<StackPanel>
<TextBlock Text="{Binding Name }" Margin="10" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
<GroupBox Header="Details">
<ContentControl cal:View.Model="{Binding SelectedViewModel}" />
</GroupBox>
</DockPanel>
</UserControl>
The answer to your specific question is yes, you can.
On the ViewModel of your UserControl. You create a property that is a ViewModel of either of the two details.
public interface IAnalyzerViewModel
{
}
Next, create two ViewModels for the Views of your Analyzer and AnalyzerGroup views.
public class AnalyzerGroupViewModel : IAnalyzerViewModel
{
}
public class AnalyzerViewModel : IAnalyzerViewModel
{
}
Next, create a property in your UserControl's ViewModel that implements INPC or PropertyChangedBase of Caliburn Micro.
public class MainViewModel :
{
private IAnalyzerViewModel _analyzerViewModel;
public IAnalyzerViewModel SelectedViewModel { get { return _analyzerViewModel; } set { _analyzerViewModel = value; OnPropertyChanged(() => SelectedViewModel); }
//Hook up the selected item changed event of your listbox and set the appropriate ViewModel to show, so if you either want to show the AnalyzerGroup or AnalyzerView.
}
And lastly, just update your MainView to
<ContentControl x:Name="SelectedViewModel"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"/>
Caliburn will hook up the appropriate bindings and stuff and will pull the View for the associated ViewModel, and also the Name convention part will automatically map it to any public property of its datacontext as long as the names match.
Hello I'm trying to dynamically change datatemplate but my method SelectTemplate in class TreeViewItemTemplateSelector never getting called (I've checked it by debugger) :( please help me :)
Code from xaml MainWindow:
Code in code behind:
Your problem seems to be that your TreeViewCustomItem is inheriting from TreeViewItem. (As seen in http://pastebin.com/jnP2nWMF)
Removing that inheritance (and the dependency property) causes the template selector to fire fine. What were/are you trying to achieve with the node item?
Looking at the OutputWindow, I get this message:
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='TreeViewCustomItem'
You don't have to have items inherit from the TreeViewItem in order to bind them to a TreeView, TreeViewItem is something that the TreeView uses to hold the data, and then the DataTemplate is used to present the data.
Move DataTemplates from TreeView.Resources to Window.Resources
<Window.Resources><DataTemplate x:Key="DefaultTemplate">
<TextBlock Text="{Binding Header}"></TextBlock>
</DataTemplate><DataTemplate x:Key="Regulation">
<TextBlock Text="{Binding Path=Header}" FontWeight="Bold"></TextBlock>
</DataTemplate>
<DataTemplate x:Key="Article">
<TextBlock Text="{Binding Path=Header}" Foreground="Green"></TextBlock>
</DataTemplate>
<local:TreeViewItemTemplateSelector x:Key="TemplateSelector" DefaultTemplate="{StaticResource DefaultTemplate}" ArticleTemplate="{StaticResource Article}" RegulationTemplate="{StaticResource Regulation}" />
and make change
<TreeView ItemTemplateSelector="{StaticResource TemplateSelector}" Height="409" HorizontalAlignment="Left" Margin="10,10,0,0" Name="treeView1" VerticalAlignment="Top" Width="277" ItemsSource="{Binding}"/>
Update code and we will see. I put similar code into VS and it works so we need to take a closer look. So i checked this and changed
public class TreeViewCustomItem
{
public string Header { get; set; }
}
and this
listmy = new ObservableCollection<TreeViewCustomItem> { new TreeViewCustomItem { Header = "xD" }, new TreeViewCustomItem { Header = "xxD" } };
//treeView1.ItemsSource = listmy;
this.DataContext = listmy;
public class selector : DataTemplateSelector
{
public DataTemplate RegulationTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
TreeViewCustomItem treeViewItem = item as TreeViewCustomItem;
if (treeViewItem.Header == "xD")
{
return RegulationTemplate;
}
else
{
return DefaultTemplate;
}
}
}
and in XAML looks like this
xmlns:local="clr-namespace:WpfApplication1.Views">
<Window.Resources>
<DataTemplate x:Key="DefaultTemplate">
<TextBlock Text="{Binding Header}"></TextBlock>
</DataTemplate>
<DataTemplate x:Key="Regulation">
<TextBlock Text="{Binding Path=Header}" FontWeight="Bold"></TextBlock>
</DataTemplate>
<local:selector x:Key="selector_" DefaultTemplate="{StaticResource DefaultTemplate}" RegulationTemplate="{StaticResource Regulation}"/>
</Window.Resources>
<Grid>
<TreeView Height="409" HorizontalAlignment="Left" Margin="10,10,0,0" Name="treeView1" VerticalAlignment="Top" Width="277"
ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource selector_}"/>
</Grid>
And it works so my presumption is that problem is inside TreeViewCustomItem.
I posted a question yesterday but I think I failed to explain it correctly.
Let me try again.
So this is my goal:
The red speech bubble represents an incoming message, and the blue bubble an outgoing message. I can describe this more precisely with the following xaml code. Note that the following code is only an explanation of what I expect to get when my actual xaml code (with some DataTemplates) compiles (WPF will populates the data automatically for me, using the DataTemplates). :
<ListBox>
<ListBoxItem HorizontalAlignment="Right">
<Grid Background="Blue">
<TextBlock Text="Help me please!" FontSize="30"/>
</Grid>
</ListBoxItem>
<ListBoxItem HorizontalAlignment="Left">
<Grid Background="Red">
<TextBlock Text="What do you want?" FontSize="30"/>
</Grid>
</ListBoxItem>
<ListBoxItem HorizontalAlignment="Right">
<Grid Background="Blue">
<TextBlock Text="I want a ListBox" FontSize="30"/>
</Grid>
</ListBoxItem>
<ListBoxItem HorizontalAlignment="Left">
<Grid Background="Red">
<TextBlock Text="Then?" FontSize="30"/>
</Grid>
</ListBoxItem>
<ListBoxItem HorizontalAlignment="Right">
<Grid Background="Blue">
<TextBlock Text="But the Grid won't fill" FontSize="30"/>
</Grid>
</ListBoxItem>
</ListBox>
In order to achive this, I wrote:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<Grid Background="{Binding Color}">
<TextBlock Text="{Binding Text}" FontSize="30"/>
</Grid>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note that alignment is not specified in the code above, becuase I really don't know how to set different alignement for ListBoxItem separately using templates. So this would result in the situation where all the blue and red grids are all aligned to the left, by default.
My first approach includes a Data Template selector (The template for incoming messages is omitted):
<ListBox>
<ListBox>
<ListBox.ItemTemplate>
<!-- local:MessageBubbleTemplateSelector.OutgoingMessageTemplate -->
<DataTemplate>
<ListBoxItem>
<Grid>
<Grid Background="{Binding Color}" HorizontalAlignment="Right">
<TextBlock Text="{Binding Text}" FontSize="30"/>
</Grid>
</Grid>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ListBox>
But this did not work. Because the Grid which wraps the speech bubble won't expand automatically, so the alignment of the Grid inside this Grid did not matter (tightly fitted).
And then I went for searching how to expand a Grid inside a StackPanel, and got no luck.
After many hours of googling and trials and errors, I decided to define the template for the ItemsPanelTemplate myself. I have a property in my Message object that can help me tell an incoming message from an outgoing one. But I don't know how to create an ItemsPanelTemplate selector (For the record, Google told me that Style.Trigger is not supported in Windows Phone 8).
So my question is: how to set different HorizontalAlignment for ListBoxItems?
BTW, ItemsPabelTemplate looks like this:
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
Thank you so much for your patience. I am madly desperate here already... So many hours wasted on this...
Note: I do not have Phone SDK so had to make do with normal WPF app. I have not used triggers as you mentioned they do not work.
So I knocked up a simple app that looks like this
Here's the code:
App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var mainvm = new MainWindowViewModel();
var window = new MainWindow
{
DataContext = mainvm
};
window.Show();
mainvm.Messages.Add(new OutgoingMessage{ MessageContent = "Help me please!"});
mainvm.Messages.Add(new IncomingMessage { MessageContent = "What do you want" });
mainvm.Messages.Add(new OutgoingMessage { MessageContent = "I want a ListBox" });
mainvm.Messages.Add(new IncomingMessage { MessageContent = "Then?" });
mainvm.Messages.Add(new OutgoingMessage { MessageContent = "But the Grid won't fill" });
}
}
MainWindow.xaml
<Window x:Class="ChatUI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ChatUI"
Title="MainWindow" Height="350" Width="200">
<Window.Resources>
<DataTemplate DataType="{x:Type local:IncomingMessage}">
<Grid Margin="0,10">
<Border CornerRadius="8" Background="Red" BorderBrush="Black" BorderThickness="1" />
<TextBlock Text="{Binding MessageContent}" HorizontalAlignment="Left" Margin="5" Foreground="White"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:OutgoingMessage}">
<Grid Margin="0,10">
<Border CornerRadius="8" Background="Blue" BorderBrush="Black" BorderThickness="1" />
<TextBlock Text="{Binding MessageContent}" HorizontalAlignment="Right" Margin="5" Foreground="White"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid Background="Black">
<ItemsControl ItemsSource="{Binding Path=Messages}"/>
</Grid>
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
MainWindowViewModel:
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
Messages = new ObservableCollection<Message>();
}
public ObservableCollection<Message> Messages { get; protected set; }
}
Message.cs:
public abstract class Message : ViewModelBase
{
private string _messageContent;
public string MessageContent
{
get
{
return this._messageContent;
}
set
{
this._messageContent = value;
this.OnPropertyChanged("MessageContent");
}
}
}
OutgoingMessage.cs
public class OutgoingMessage : Message
{
}
IncomingMessage.cs
public class IncomingMessage : Message
{
}
How this works
I override the application startup so I can create viewmodels to populate my UI. You can see in the App.xaml.cs code I create the Window and show it, and then add the messages. I was going to use a timer but got lazy.
If you look at the MainWindow.xaml, you will notice that I have 2 DataTemplates defined. One of them targets my IncomingMessageViewModel and the other targets the OutogingMessageViewModel. The local prefix is an alias for my application namespace. I have an ItemsControl that can contain the base type Message class, just so that I can have both Incoming and Outgoing messages in the same collection. This is bound to the Messages property on my MainWindowViewModel class. It is important to have incoming and outgoing messages as 2 separate classes as this is the magic that makes this work.
An alternative technique would be to use a property with a style selector bound to the property as one of the other answers suggest, but this would mean that I would have to deal with UI specific logic in my ViewModel (which I don't like to do).
To change the appearance of either Message type, just change the xaml code in the respective DataTemplate.
Hope this helps.
In WPF you would need to add
<ListBox.ItemContainerStyle>
<Style>
<Setter Property="HorizontalAlignment" Value="{Binding WHATEVER}" />
</Style>
</ListBox.ItemContainerStyle>
to your ListBox and set the "WHATEVER" to a property of your items that has the alignment specified... I don't know if that works for Windows Phone but it seems worth a try since you didn't mention the ItemContainerStyle...
Instead of using Grid, use DockPanel with HorizontalAlignment="Stretch" instead.
As for the data alignment, assuming you are using ItemsSource, then there is some workaround.
First, the easy thing to do is to add HorizontalAlignment WPF property to your message class. The message class will determine whether the HorizontalAlignment will be left or right. However this will make dependency more higher with the UI.
The code will be like this:
<TextBlock Text="{Binding Text}" HorizontalAlignment="{Binding MessageHAlign}" />
Second, the better (or clean) way to do is to do HorizontalAlignment binding with converter (IValueConverter). It is harder and you must define your own converter, but your code will be tidier. Then your message has an enum of Income or Outcome message, named MessageType. Then in your converter define it like:
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if(parameter is MessageType){
if(((MessageType)parameter) == MessageType.Income){
return HorizontalAlignment.Left;
}
else{
return HorizontalAlignment.Right;
}
}
}
The code above is not tested, so please consider error.
For the implementation of Converter, please search it in some places. I still cannot generate Converter binding without help source :)
Try this and set Property according to incoming as Left and Outgoing as Right
<DataTemplate>
<ListBoxItem>
<Grid>
<Grid Background="{Binding Color}" HorizontalAlignment="{Binding Property}">
<TextBlock Text="{Binding Text}" FontSize="30"/>
</Grid>
</Grid>
</ListBoxItem>
</DataTemplate>
that would be my dirty working example
code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var l = new List<lItem>();
for(int i=0;i<5;i++)
{
l.Add(new lItem(true,"aaa"+i));
l.Add(new lItem(false,"bbb"+i));
}
sads.ItemsSource = l;
}
}
public class lItem
{
public string Text { get; set; }
public Brush Color { get; set; }
public HorizontalAlignment alig { get; set; }
public lItem(bool ss, string str)
{
Text = str;
Color = Brushes.Blue;
alig = HorizontalAlignment.Right;
if (ss)
{
Color = Brushes.Red;
alig = HorizontalAlignment.Left;
}
}
}
Xaml
<ListBox Name="sads" Width="230">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<Grid Width="200">
<Label Background="{Binding Color}" VerticalAlignment="Top" HorizontalAlignment="{Binding alig}" >
<TextBlock Text="{Binding Text}" FontSize="30"/>
</Label>
</Grid>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
i would recommend to use trigger instead of define visual parts in your ViewModel
I have 3 models: Machine, Part, and Component. On my ui, I have bound one listbox to a list of Machines and a second listbox to the selected Machine's list of Parts. Now when the user selects a part, I would like to bind a datagrid to the selected part's list of components. I tried setting the datagrid's itemssource to:
ItemsSource="{Binding ElementName=PartSelectList, Path=SelectedItem.Components}"
I also tried setting its datacontext and itemssource
DataContext={Binding ElementName=PartSelectList, Path=SelectedItem}
ItemsSource={Binding Components}
However the datagrid does not automatically update if changes are made to the part's list of components via methods such as
part.Bags.Add(new Component(..));
Anyone have suggestions on how I should approach this?
Update: I have a class MachineCollection which implements INotifyPropertyChanged. In my xaml.cs code I create my MachineCollection object and bind the MainGrid to it's Machines property:
Page1.xaml.cs:
private MachineCollection machines
public Page1()
{
machineCollection = new MachineCollection();
MainGrid.DataContext = machineCollection.Machines
actionThread = new Thread(InitLists);
actionThread.Start();
}
public void InitLists()
{
machineCollection.Machines.AddRange(dbCon.GetAllMachines());
}
Classes:
public class MachineCollection : INotifyPropertyChanged
{
public List<Machine> Machines
{
get { return machines; }
set { machines = value; NotifyPropertyChanged("Machines"); }
}
}
public class Machine : INotifyPropertyChanged
{
public List<Part> Parts
{
get { return parts; }
set { parts = value; NotifyPropertyChanged("Parts");
}
}
public class Part : INotifyPropertyChanged
{
public List<Component> Components
{
get { return components; }
set { components = value; NotifyPropertyChanged("Components");
}
}
public class Component : INotifyPropertyChanged
{
public int Length
{
get { return length; }
set { length = value; NotifyPropertyChanged("Length");
}
}
XAML:
<Page>
<Grid Name="MainGrid">
<ListBox x:Name="MachineSelectList" HorizontalAlignment="Left" VerticalAlignment="Bottom"
Height="114" Width="231"
Foreground="White" Background="#FF7A7A7A" FontSize="16" BorderBrush="#FFC13131"
ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="200">
<TextBlock Text="{Binding SerialNumber}" TextAlignment="Left" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<Button Name="DeleteMachine" Tag="{Binding SerialNumber}" HorizontalAlignment="Right" VerticalAlignment="Center" Height="20"
Background="{x:Null}" BorderBrush="{x:Null}" Foreground="{x:Null}" Focusable="False" Click="Btn_Click" >
<Image Source="..\Resources\Images\delete.png" Stretch="Uniform"/>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Name="PartSelectList" HorizontalAlignment="Left" VerticalAlignment="Bottom"
Height="164" Width="231"
Background="#FF7A7A7A" Foreground="White" FontSize="16" BorderBrush="#FFC13131"
ItemsSource="{Binding Path=Parts}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="200">
<TextBlock Text="{Binding PartNumber}" TextAlignment="Left" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<Button Name="DeletePart" Tag="{Binding PartNumber}" HorizontalAlignment="Right" VerticalAlignment="Center" Height="20"
Background="{x:Null}" BorderBrush="{x:Null}" Foreground="{x:Null}" Focusable="False" Click="Btn_Click" >
<Image Source="..\Resources\Images\delete.png" Stretch="Uniform"/>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Page>
One way to solve this is using ObservableCollection, which is made for situations like this.
public ObservableCollection<Part> Parts
{
get { return parts; }
set { parts = value; NotifyPropertyChanged("Parts");
}
Or you could implement the interface INotifyCollectionChanged manually
The problem is, basically, that the binding system does not know when you call Add. If you were reassigning the Parts property each time, then the system would just redraw everything, so it would reflect the changes too, but if ObservableCollection is not a problem, that's the prefered way