I'm new to WPF MVVM and I've been working my way to an almost complete application. There's only one issue, and I can't seem to figure out what I'm doing wrong. Let me add that I've only got a Winforms background and Databinding is something I've never needed to do, so if the solution seems obvious, that's why.
<!-- The list of download packages. -->
<ListBox x:Name="PackagesList" DockPanel.Dock="Left" Width="120" ItemsSource="{Binding ViewRaster.RasterPackages}">
<!-- Each individual package -->
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Image Height="16" Width="16" Source="{Binding PackageImage}"/>
<TextBlock Text="{Binding PackageName}"/>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Tag" Value="{Binding PackageDownloads}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
I've got a list of packages within the object ViewRaster as you can tell from the ItemSource of the listbox. With each package of the item source I create a listbox item that contain an image and a textblock with the name of the package, and the image of the package.
Now next, there's the "ItemContainerStyle", which I assumed would have worked the exact same way -- namely, that I could use the properties of each individual package, as was bound from the ItemsSource.
I do not seem to be able to access the individual "Package" as I would within the ItemTemplate -> DataTemplate. It's neccessary that I have the ListBoxItem have either a "Tag" or a "DataContext" set to the "PackageDownloads".
From the designer it's telling me that it's unable to find "PackageDownloads" in the Data Context of my View, but I'm not in the DataContext of my view, I'm in the DataContext of the ItemsSource.
Why is this? How can I fix this?
You don't need to explicitly set a ListBoxItems's DataContext or its Tag property to get access to the selected item from the RasterPackages collection.
The selected RasterPackage object is directly accessible via the ListBox's SelectedItem property:
<ListView
ItemsSource="{Binding ElementName=PackagesList, Path=SelectedItem.PackageDownloads}">
Related
I am trying to set the Visibility property of a control from an Item inside an ItemControl based on the number of similar items - items with the same property.
I am not using a MVVM pattern, the bindings are done in Code Behind.
Detailing the subject:
I have an ItemsControl that gets populated in Code Behind from a CollectionView that has its source from ObservableCollection. The CollectionView has an GroupDrescription based on a property of the class named displaytip:
var collectionViewA = new CollectionViewSource { Source = PartiGUI }.View;
PropertyGroupDescription groupDescription = new PropertyGroupDescription("displaytip");
collectionViewA.GroupDescriptions.Add(groupDescription);
icPartiGroupedA.ItemsSource = collectionViewA;
In the XAML code, I have a HeaderTemplate so the items in the CollectionView are displayed using grouping.
I want to set the Visibility property of a control inside the Item Control based of the number of items in the CollectionView with the same Group Name.
If the item is the only one in the group I don't want the control to be visibile. If there are more items, I want the control to be visibile in each one of them.
At first, I was thinking about using triggers. I have put a hidden control in Header that binds to the item count of that group. I have used this approach because this is the only place where I can get the ItemCount of the items inside the same group. Then, at the trigger, I have tried to identify the control by name.
<ItemsControl x:Name="icPartiGroupedA">
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
<TextBlock x:Name="ElementeGrupateCount" Text="{Binding ItemCount, UpdateSourceTrigger=PropertyChanged}"></TextBlock>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Stackpanel>
<TextBox Text="{Binding SomeProperty}"/>
<StackPanel>
<Button>TestButton1</Button>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ElementeGrupateCount, Path=Text, UpdateSourceTrigger=PropertyChanged}" Value="1">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel >
</StackPanel >
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Using this trigger, I receive this error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=ElementeGrupateCount'. BindingExpression:Path=Text; DataItem=null; target element is 'StackPanel' (Name=''); target property is 'NoTarget' (type 'Object')
I am having troubles getting this to work and I think that my approach is bad because the control that I want to reference is placed inside the header.
Is there a way to use this exclusively with Trigger?
I think a cleaner approach would be to use a converter and to place the filter inside the converter but I can't find a way to pass pass the CollectionView or the ObservableCollection in the converter.
I need to create automation system-level test for control.
Control is set by style and have ListView with separate ListViewItem template.
And my goal is to get text from the header inside this ListViewItem.
Here is what I have in XAML (code simplified)
<Style>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border>
<Grid>
<ScrollViewer x:Name="ScrollViewer">
<ScrollViewer.TopHeader>
<StackPanel>
<ListView ItemsSource="{TemplateBinding SomeSource}"
ItemTemplate="{StaticResource MyTemplate}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</StackPanel>
</ScrollViewer.TopHeader>
</ScrollViewer>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And template for ListViewItem is something like:
<DataTemplate>
<Grid>
<Grid>
<TextBlock x:Name="TextName"
AutomationProperties.AutomationId="{Binding SomeId}"
Text="{x:Bind SomeText}"/>
</Grid>
</Grid>
</DataTemplate>
Of course ListViewItems are generating automatically in runtime. They are binded to according property and got needed template.
But in test I can't reach my TextBlock anyhow (ById, FindElementById and so on).
I can find elements by firstly finding ListView by class (FindElementByClassName), then ListViewItems by class, then TextBlock by class, but I think it is not the right way. Because in future structure of control could be changed and it will be harder to support tests.
Control have AutomationPeer as the Grids too.
So do you have any ideas why I can't get my simple TextBlock or even ListViewItems by their AutomationId?
Problem was resolved by creating custom Automation peer for conrtol. Control inherits ListView (wasn't clear in problem description). So there was problems with listview inside listview-inherited control by Automation id.
Also was helpfull adding Thread.Sleep() or Task.Delay() for a couple of seconds after element shown but before element finding by WinAppDriver.
Looks like test environment need more time to bind and find elements.
I have a program with the following schema.
The program starts with a login, once the user puts the name and password it directs to the main window (no problem passing the values there as it is between forms). This windows uses a MVVM schema to load the ContentControl necessary in that moment.
<Window.Resources>
<DataTemplate ...>
</DataTemplate> (several times)
</Window.Resources>
<DockPanel>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding MenuItems}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
<TextBlock Text="{Binding Header}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
<ContentControl Content="{Binding CurrentPageViewModel}" >
</ContentControl>
</DockPanel>
As you can see from the code, it has a binded menu (which does not change) and a ContentControl, which calls a UserControl depending on what button has been pressed on the menu. The ContentControl is binded and uses a MVVM model to load the UserControl.
The problem is that the UserControl is not using a MVVM schema (only the loading part uses the MVVM). They are directly operating in c# using a direct WPF model (.cs and .xaml), so the UserControl ViewModel does not bind anything in the Model UserControl.
I want to find a way to pass the login information to the called UserControl. In a way that every time it changes the ContentControl, the new UserControl will have acces to the login data.
Is there any way to create a global-like variable that can be posted by the login or the main window, and read by each of the UserControls?
It is not exactly what i wanted but i did the trick by using the project properties.
I set it in the loggin and get it in the window i want.
object myProperty = App.Current.Properties["Test"];
I'm working on a project (for Windows Phone 8 with Visual Studio 2012 with C#) where I want to display some items that each have:
a picture
a title
a description
to be able to be clicked (so that I can navigate to a certain Page)
So I thought I could do that with a stackpanel. But I'm not sure how I can add items that have the above properties and to be able to add those items from XAML. I tired adding items through a ItemsControl in stackpanel but I'm not sure how I can add more complex items like the one I want.
The best approach is to use a ListBox or LongListSelector rather than a StackPanel. You can then:
Data bind the list to the control itself, which will handle adding/deleting items from the control automatically
Define the view for each control using ListBox's ItemTemplate property
First of all, in your code-behind/ViewModel/what-have-you, you'll want to create an ObservableCollection of objects to display. ObservableCollection will let the control know to update in the case an item is added, removed, etc.
public ObservableCollection<T> foo = new ObservableCollection<T>();
In XAML, you'll then want to databind this ObservableCollection to the ListBox you've created:
<ListBox x:Name="ListBox" ItemsSource="{Binding foo}" />
Finally, you can define the ItemTemplate of the ListBox like so:
<ListBox x:Name="ListBox" ItemsSource="{Binding foo}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock Text="{Binding Title}" />
<TextBlock Text="{Binding Description}" />
<Image Source="{Binding Image}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'd highly recommend reading this guide, especially "Binding a control to a collection of objects" and the section after on DataTemplates. :)
My WPF Windows contains a TabControl which displays content on different tabs. A click on the button below executes a method via ICommand interface / Binding. The called method generates text which is intended to be displayed in the second tab.
How can I switch to the second tab on button click without violating the MVVM Pattern?
I tried to bind the TabItem.IsSelected Property to something in my ViewModel but I wanted to use the other tabs (tab1) as well.
Any thoughts?
I found it out by myself.
The key is a two way binding. When the button is clicked it sets the property DisplayXamlTab true. The IsSelected attribute is bound to this variable. if another tab is clicked the binding will set the DisplayXamlTab Property to false.
Note: UpdateSourceTrigger=PropertyChanged is also very important
Code comes below:
XAML:
<TabItem Header="XAML" IsSelected="{Binding DisplayXamlTab, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<Grid Background="#FFE5E5E5">
<TextBox x:Name="TxtXamlOutput" IsReadOnly="True" Text="{Binding XamlText, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible"/>
</Grid>
</TabItem>
C# Property:
private bool displayXamlTab;
public bool DisplayXamlTab
{
get { return this.displayXamlTab; }
set
{
this.displayXamlTab = value;
this.RaisePropertyChanged("DisplayXamlTab");
}
}
if you're going the MVVM way you're going to create two dependency properties in the code behind:
ObservableCollection<ItemType> Items;
ItemType MySelectedItem;
Then, bind the TabControl ItemsSource property to the Items and bind the SelectedItem property to MySelectedItem
<TabControl ItemsSource="{Binding Items}"
SelectedItem="{Binding MySelectedItem, Mode=TwoWay}">
<TabControl.ItemTemplate>
<DataTemplate>
<... here goes the UI to display ItemType ... >
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
When you want to change the selected tab, simply update the MySelectedItem dependecy property
Although this question is fairly old and well answered already, I thought I'd add this additional answer to demonstrate an alternative way of changing the selected TabItem in a TabControl. If you have a view model for each TabItem, then it can be helpful to have an IsSelected property in it to determine whether it is selected or not. It is possible to data bind this IsSelected property with the TabItem.IsSelected property using the ItemContainerStyle property:
<TabControl ItemsSource="{Binding MenuItems}" TabStripPlacement="Top">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type ControlViewModels:MenuItemViewModel}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageSource}" Margin="0,0,10,0" />
<TextBlock Text="{Binding HeaderText}" FontSize="16" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type ControlViewModels:MenuItemViewModel}">
<ContentControl Content="{Binding ViewModel}" />
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
You can now change the selected TabItem from the parent view model like this:
MenuItems[0].IsSelected = true;
Note that because this property is data bound to the TabItem.IsSelected property, calling this...:
MenuItems[1].IsSelected = true;
... will infact also automatically set the MenuItems[0].IsSelected property to false. so if the view model that you are working with has its IsSelected property set to true, then you can be sure that its related view is selected in the TabControl.
You can create a binding between the view model and the TabControl.SelectedIndex property - i.e., 0 selects the first TabItem , 1 selects the second, etc.
<TabControl DataContext="..." SelectedIndex="{Binding SomeVmProperty}" ...
(alternatively, depending on how you've got things set up, you could bind against SelectedItem...)
You'll likely want to use some sort of "Event Aggregator" pattern (I.e. the Messenger class in MVVM Light) to broadcast some sort of "navigation" message. Your View - the TabControl - can listen for the specific message, and navigate to Tab2 when the message is received.
Alternatively, you can bind the "SelectedItem" property of the TabControl to your ViewModel, and simply call CurrentTab = MySecondTabViewModel from within your VM. This is the approach recommended by #HighPoint in the comments to the OP, but I'm not a fan; see below. Another caveat to this approach is that you need to be familiar with DataTemplates, as you will need to map a view to each ViewModel which you display.
I personally like the first approach, because I don't consider it to be a "responsibility" of the ViewModel to handle tab navigation. If you simply alert your View when data changes in your ViewModel, you allow the View to decide whether or not it wants to change tabs.