I've faced with issue, using ScrollViewer.
Here's sample view models:
public class A
{
public string Text { get; set; }
}
public class B
{
public int Number { get; set; }
}
...and DataTemplateSelector:
public class ViewModelTemplateSelector : DataTemplateSelector
{
public DataTemplate ATemplate { get; set; }
public DataTemplate BTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is A)
return ATemplate;
if (item is B)
return BTemplate;
return base.SelectTemplate(item, container);
}
}
XAML:
<Grid>
<Grid.Resources>
<local:ViewModelTemplateSelector x:Key="ViewModelTemplateSelectorKey">
<local:ViewModelTemplateSelector.ATemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</local:ViewModelTemplateSelector.ATemplate>
<local:ViewModelTemplateSelector.BTemplate>
<DataTemplate>
<TextBox Text="{Binding Number}"/>
</DataTemplate>
</local:ViewModelTemplateSelector.BTemplate>
</local:ViewModelTemplateSelector>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ListBox x:Name="ListBox" ItemsSource="{Binding}"/>
<ScrollViewer Grid.Row="1" Content="{Binding SelectedItem, ElementName=ListBox}"
ContentTemplateSelector="{StaticResource ViewModelTemplateSelectorKey}"/>
<ContentControl Grid.Row="2" Content="{Binding SelectedItem, ElementName=ListBox}"
ContentTemplateSelector="{StaticResource ViewModelTemplateSelectorKey}"/>
</Grid>
This is what is going on, when any item is selected in ListBox:
As you can see, ScrollViewer ignores ContentTemplateSelector, while ContentControl does not. ScrollViewer is inherited from ContentControl, and at first look, there's no reason for such behavior.
I know, that if I'll declare implicit data templates for A and B, ScrollViewer will handle them correctly, but this is not an option for my real application.
Is this known bug? Or am I missing something?
UPD.
I've submitted an issue on MS Connect.
I did not test the syntax. If it is wrong just let me know and I will delete
This is what I would try
<ScrollViewer Grid.Row="1">
<ContentControl Content="{Binding SelectedItem, ElementName=ListBox}"
ContentTemplateSelector="{StaticResource ViewModelTemplateSelectorKey}"/>
</ScrollViewer>
This should do the trick:
<ScrollViewer Grid.Row="1">
<ContentPresenter Content="{Binding SelectedItem, ElementName=ListBox}" ContentTemplateSelector="{StaticResource ViewModelTemplateSelectorKey}" />
</ScrollViewer>
Related
Let me begin with apologizing for the perhaps somewhat vague title, I'm having a hard time explaining my problem! Perhaps this is why I'm hardly getting any Google results, with this post being the closest to my problem (I think): How to bind to a property of the ViewModel from within a GridView
Anyways, I have a list of news articles that are being dynamically generated. The user has the option to press a "Star"-button in order to add an article to his/her favorites list. This "Star"-button should thus only be visible when the user is logged in.
I'm trying to achieve this by setting the Visibility of the Button to a property called IsLoggedIn inside my ViewModel. However, because this is happening inside my ListView it's trying to find the property IsLoggedIn inside of Article instead of the ViewModel.
So I guess my questions boils down to: How can I bind to a ViewModel property inside of a Databound ListView?
<ListView ItemsSource="{x:Bind VM.Articles, Mode=OneWay}" ItemClick="DebuggableListView_ItemClick" IsItemClickEnabled="True" SelectionMode="None" Grid.Row="1" VerticalAlignment="Top">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:Article">
<Grid Margin="0,10,10,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="4*"/>
</Grid.ColumnDefinitions>
<Image Source="{Binding Image}" Margin="0,0,10,0" Grid.Column="0" VerticalAlignment="Top" ImageFailed="Image_ImageFailed"/>
<Button Visibility="{x:Bind VM.IsLoggedIn, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}" FontFamily="Segoe MDL2 Assets" Content="" FontSize="30" Background="Transparent" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
<StackPanel Grid.Column="1">
<TextBlock TextWrapping="Wrap" FontWeight="Bold" Text="{Binding Title}"/>
<TextBlock TextWrapping="Wrap" Text="{Binding Summary}"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Requested Article class:
public sealed class Article
{
public int Id { get; set; }
public int Feed { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
public DateTime PublishDate { get; set; }
public string Image { get; set; }
public string Url { get; set; }
public string[] Related { get; set; }
public Category[] Categories { get; set; }
public bool IsLiked { get; set; }
}
Okay, so currently I got it working by having a property which gets the singleton of my VM, however I'm sure there has to be a cleaner way to get something simple like this working. I've added a sample rar (OneDrive link: https://1drv.ms/u/s!Ar4fOTiwmGYnyWbwRY6rM0eFsL9x) which has a list, a ViewModel, some dummy data and a Visibility property inside the VM. If you can get it working without my dirty method please feel free to commit as an answer.
This type problem can best be solved using relative binding. Rather than binding directly to the current DataContext, i.e. this case the individual item in the ListView's ItemsSource, you can bind to a property of any ancestor element.
Given a simple ViewModel class
public class ViewModel: ViewModelBase
{
private bool _isLoggedIn;
public bool IsLoggedIn
{
get { return _isLoggedIn; }
set
{
_isLoggedIn = value;
RaisePropertyChanged(() => IsLoggedIn);
}
}
public IEnumerable<string> Items
{ get { return new[] {"One", "Two", "Theee", "Four", "Five"}; } }
}
the View can then be defined as
<Window x:Class="ParentBindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ParentBindingTest"
Title="MainWindow"
Width="300" Height="400">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<CheckBox Margin="16,8" HorizontalAlignment="Left" Content="Logged In?" IsChecked="{Binding IsLoggedIn, Mode=TwoWay}" />
<ListView Grid.Row="1" Margin="16,8" ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Visibility="{Binding Path=DataContext.IsLoggedIn, RelativeSource={RelativeSource AncestorType={x:Type ListView}}, Converter={StaticResource BooleanToVisibilityConverter}}">
<Image Source="Images\remove.png" />
</Button>
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
Note how in the item DataTemplate, the TextBlock's Text property is bound directly to the item.
However the Button's Visibility is bound not to a propery of it's own DataContext, but to that of the ListView itself, using relative binding.
I have a GridView with image, to tap on any of them, you open a FlipView with images of GridView can watch with the swipe. I wish I could do the tapping on an image of the grid and bring up the control FlipView already with that image previously selected, and does not begin to always display the first image of the list. how can I do?
This is XAML code:
<FlipView x:Name="FlipBig" Visibility="Collapsed" ItemsSource="{Binding Source={StaticResource CVSOpere}}" Grid.Column="0" Grid.Row="0" Width="295" Height="340">
<FlipView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="290"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="Gray">
<Image Source="{Binding URLOpera}" ImageFailed="Image_ImageFailed" Margin="3,3,3,3" Grid.Row="0" />
</Border>
<Border Grid.Row="1" Background="Gray" Margin="0,10,0,0" >
<TextBlock Text="{Binding NomeOpera}" Foreground="White" HorizontalAlignment="Center" FontSize="17" TextWrapping="Wrap"/>
</Border>
</Grid>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
this is my class in c#:
namespace App1
{
class MuseoOpera
{
public string NomeMuseoContenuta { get; set; }
public string NomeOpera { get; set; }
public string URLOpera { get; set; }
}
}
The usercontrols are considered derived types, and do not inherit styles implicitly.
One thing you can do is set the style on the UserControl initialization
Style = (Style)FindResource(typeof (UserControl));
I have a pretty complex question:
At my work, we use WPF with MVVM standards.
On one of our UserControls, there is a treeview that loads all the tables from the database. For each table, the name is loaded in the list by the treeview. When you click on the table name, you can add data to that table from the screen.
Basically, it loads the column names from the table on the side, and lets you enter data and save. The records are then saved and added as children to the table. From here, selecting a child lets you update that info.
Now, the current app loads the data fine. The table names and data are loaded. The child data loads.
Everything is fine, but when it comes to loading the controls, we only use Textboxes.
On the usercontrol the code is:
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate x:Key="AdditionalItemsTemplate">
<Border>
<StackPanel>
<Label
Content="{Binding Name}"
Style="{StaticResource PanelLabelStyle}"/>
<TextBox
Text="{Binding Value}"
Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
</Border>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Border
Height="350"
Width="{Binding Width}"
Style="{StaticResource InnerMenuBorderStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border
Grid.Row="0"
Grid.Column="0"
Width="230"
IsEnabled="{Binding IsUpdateEnabled}">
<StackPanel>
<Border
Margin="3,3,3,0"
Style="{StaticResource PanelBorderStyle}">
<StackPanel>
<Label Style="{StaticResource PanelLabelStyle}">
<TextBlock
Text="{Binding TableName}"
TextWrapping="WrapWithOverflow"/>
</Label>
<TextBox
Style="{StaticResource TextBoxStyle}"
Text="{Binding Value}"
TextWrapping="Wrap"/>
<ItemsControl
Width="222"
HorizontalAlignment="Left"
ItemsSource="{Binding additionalFields}" Margin="0,0,-226,0"
ItemTemplate="{StaticResource AdditionalItemsTemplate}"/>
</StackPanel>
</Border>
<Button
Width="70"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="3.5,0,0,0"
Template="{StaticResource UpdateButtonTemplate}"
Command="{Binding ButtonCommand}"
CommandParameter="Update"/>
</StackPanel>
</Border>
<Rectangle
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Width="2"
StrokeDashArray="0.5 1.0 0.3"
Stroke="LightGray"
Visibility="{Binding IsAddVisible}"/>
<Border
Grid.Row="0"
Grid.Column="2"
Width="230"
Visibility="{Binding IsAddVisible}">
<StackPanel>
<Border
Margin="3,3,3,0"
Style="{StaticResource PanelBorderStyle}">
<StackPanel>
<Label
Grid.Row="0"
Content="{Binding LabelText}"
Style="{StaticResource PanelLabelStyle}"/>
<TextBox
Grid.Row="1"
Text="{Binding NewLookup}"
Style="{StaticResource TextBoxStyle}"/>
<ItemsControl
Width="222"
Margin="0,0,-226,0"
HorizontalAlignment="Left"
ItemsSource="{Binding childAdditionalFields}"
ItemTemplate="{StaticResource AdditionalItemsTemplate}"/>
</StackPanel>
</Border>
<Button
Grid.Row="2"
Width="50"
HorizontalAlignment="Left"
Margin="3.5,0,0,0"
Template="{StaticResource AddButtonTemplate}"
Command="{Binding ButtonCommand}"
CommandParameter="Add"/>
</StackPanel>
</Border>
</Grid>
</Border>
In the resources, the AdditionalItemsTemplate loads a label for each column name, and the textbox is loaded with the values, or can be used to enter new values
I would like to make just ONE datepicker for ONE table.
So for example. In table Students, the fields are Name (varchar), Age (varchar), DateStarted (date). A textbox loads for Name and Age, but just for DateStarted a datepicker should load.
So far, in the resources, i add <local:DatePicker
SelectedDate="{Binding Value}"
Visibility="{Binding DateVisible}"/>
So that when the table Student is loaded, the visibility of the datepicker is changed from being hidden to visible.
Here is a sample of the ViewModel:
public AddUpdateConfigurationViewModel(TreeViewContainer treeViewContainer, List<AddChangeSiteConfigurationViewModel> lookupTypeList, FieldDataResponse fieldDataResponse)
{
NewLookup = String.Empty;
Width = 480;
DateVisible = "Hidden";
IsAddVisible = "Hidden";
IsUpdateEnabled = false;
if (treeViewContainer.AdditionalFields != null)
{
if (treeViewContainer.AdditionalFields.Count() > 0)
{
treeViewContainer.AdditionalFields.RemoveAll(x => x.Name.Contains("ID"));
treeViewContainer.AdditionalFields.RemoveAll(x => x.Name.Contains("Guid"));
}
}
if (treeViewContainer.AdditionalFields != null)
this.additionalFields = new ObservableCollection<TreeViewContainer>(treeViewContainer.AdditionalFields);
if ((treeViewContainer.ParentTable == null) | (treeViewContainer.ParentTable == String.Empty))
IsUpdateEnabled = true;
var lookupTypes = lookupTypeList.Where(x => x.Parent_Field == "ID" + treeViewContainer.TableName);
if (treeViewContainer.ParentTable == "IsParent")
{
IsAddVisible = "Visible";
DateVisible = "Hidden";
LabelText = "New " + treeViewContainer.Name;
TableName = treeViewContainer.TableName;
childAdditionalFields = new ObservableCollection<TreeViewContainer>();
foreach (var additionalField in treeViewContainer.AdditionalFields)
childAdditionalFields.Add(new TreeViewContainer(additionalField.Name));
if (treeViewContainer.Name.Equals("Student"))
{
DateVisible = "Visible";
}
}
else if (lookupTypes.Count() > 0)
{
foreach (var lookupType in lookupTypes)
{
IsAddVisible = "Visible";
LabelText = "New " + lookupType.Name;
TableName = lookupType.TableName;
childAdditionalFields = new ObservableCollection<TreeViewContainer>();
foreach (var additionalChildField in lookupType.additionalFieldsDictionary)
childAdditionalFields.Add(new TreeViewContainer(additionalChildField.Key));
}
}
else
Width = 250;
this.Name = treeViewContainer.Name;
this.Value = treeViewContainer.Name;
this.ID = treeViewContainer.ID;
this.treeViewContainer = treeViewContainer;
this.lookupTypeList = lookupTypeList;
this.fieldDataResponse = fieldDataResponse;
}
I hope this makes sense.
Now when i load the usercontrol and select on another table, sometimes the datepicker is visible, even though the visibility is set to Hidden.
And when the Student table is selected, there are datepickers under textboxes for every field.
How do I go about just making one datepicker for this one table?
If there is any more info needed, I will edit and update accordingly
EDIT
Screenshots
This is how the treeview loads. all the tables in the db are listed here
now clicking on a table that shouldn't have the datepicker does this:
it adds the datepicker to this treeview container. i want to eliminate this,
and this is where it should be
but i have too many, see. there should only be one added here. not all the way down, under every textbox.
and the Visible setting is just a temp fix, until i get this datepicker sorted. the visibility can be tackled later
I don't think adding a datepicker and trying to show/hide it is the best way to go. A better way is to load a datatemplate which matches the type which is being bound to. So if the column is a datetime type, you should load a datatemplate which has a label and a datepicker. This is what the DataTemplateSelector is for. You create a class which extends DataTemplateSelector, and decide which template to return, based on the object being bound to.
<UserControl.Resources>
<DataTemplate x:Key="DateTimeFieldTemplate">
<Border>
<StackPanel>
<Label
Content="{Binding Name}"
Style="{StaticResource PanelLabelStyle}"/>
<local:DatePicker
SelectedDate="{Binding Value}"/>
</StackPanel>
</Border>
</DataTemplate>
<DataTemplate x:Key="TextFieldTemplate">
<Border>
<StackPanel>
<Label
Content="{Binding Name}"
Style="{StaticResource PanelLabelStyle}"/>
<TextBox
Text="{Binding Value}"
Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
</Border>
</DataTemplate>
<local:TemplateSelector x:Key="TemplateSelector" DateTimeFieldTemplate="{StaticResource DateTimeFieldTemplate}" TextFieldTemplate="{StaticResource TextFieldTemplate}"/>
</UserControl.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Items}" ItemTemplateSelector="{StaticResource TemplateSelector}"/>
</Grid>
You illustrate in your original code that you have a Name and Value property, so you only need to decide on how to provide information on what datatype the Value property is. One way is to make an interface with a Value property of type object:
public interface IBoundDataColumn
{
public string Name { get; } // Not really necessary for the template selector, but perhaps for completeness
public object Value { get; }
}
public class DateTimeColumn : IBoundDataColumn
{
public string Name { get; set; }
public object Value { get; set; }
}
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate TextFieldTemplate
{
get;
set;
}
public DataTemplate DateTimeFieldTemplate
{
get;
set;
}
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
IBoundDataColumn boundCol = item as IBoundDataColumn;
if (boundCol.Value.GetType() == typeof(DateTime))
{
return DateTimeFieldTemplate;
}
else
{
return TextFieldTemplate;
}
return base.SelectTemplate(item, container);
}
}
Or you could have a DataType property:
public interface IBoundDataColumn
{
public Type DataType {get;}
}
public class DateTimeColumn : IBoundDataColumn
{
public string Name {get;set;}
public DateTime Value {get;set;}
public Type DataType { get { return typeof(DateTime); } }
}
Or you could avoid the interface altogether and use reflection to get the type dynamically:
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
var p = item.GetType().GetProperty("Value");
if (t.PropertyType == typeof(DateTime))
...
}
I have those two classes:
class DownloadLink
{
public string Name { get; private set; }
public string Url { get; private set; }
//(...)
}
class DownloadGroup
{
public List<DownloadLink> Links { get; private set; }
//(...)
}
class Manager
{
public List<DownloadGroup> Groups { get; private set; }
}
Manager managerOBJ = new Manager();
I want to display this like that:
Everything will be in ListBox:
I wan to bind managerOBJ.Groups to that ListBox. - How to do it?
Than I want to create DataTamplate to display each group and all links in that group. - How to do it?
I want to do as much as possible from XAML
UPDATE:
This is what I got. It's not workig. List box is empty.
<ListBox DockPanel.Dock="Right" VerticalAlignment="Stretch" Width="500" HorizontalAlignment="Right" Background="#FFE1FFF5" HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Visible" ItemsSource="{Binding Path=Groups}" Name="GroupsListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Height="30" VerticalAlignment="Top" Width="500" >
<Grid Height="Auto" Width="500">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="XX MB w XX plikach" HorizontalAlignment="Stretch" Margin="0"/>
</Grid>
<ListBox HorizontalAlignment="Stretch" Height="43" Margin="0,5,0,0" Width="Auto" VerticalAlignment="Top" ItemsSource="{Binding Path=Links}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and in code behid I have:
RapideoAccount = new Rapideo();
GroupsListBox.DataContext = RapideoAccount;
The whole manager is contained in a listbox, for each downloadgroup in the manager you add an itemscontrol that contains another items control with the links in it.
This can be done by using DataTemplates:
<ListBox Name="myGroups"
ItemsSource="{Binding Path=Groups}">
<!-- each List<DownloadGroup> in the manager: -->
<ListBox.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Path=Links}">
<!-- each Link in the Downloadgroup -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Url}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In code you would put:
Manager managerOBJ = new Manager();
myGroups.DataContext = managerOBJ;
define managerOBJ as a property in your viewmodel
binding viewmodel to your view.
binding ListBox itemssource to managerOBJ.Groups.
define DataTemplate inside ListBox to display each DownloadGroup.
I have using Silverlight and Caliburn Micro and am having a problem getting child nodes to appear in the tree. The TreeView is contaimed in a grid and here is my XAML excerpt:
<Grid.Resources>
<sdk:HierarchicalDataTemplate x:Key="AccountTemplate">
<TextBlock Text="{Binding AccountNumber}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate x:Key="CategoryTemplate"
ItemsSource="{Binding CategoryServices}"
ItemTemplate="{StaticResource AccountTemplate}">
<TextBlock Text="{Binding Path=CategoryName}" FontWeight="Bold" />
</sdk:HierarchicalDataTemplate>
</Grid.Resources>
<Controls:TreeView Grid.Row="1" Grid.Column="0"
ItemsSource="{Binding FromAddressServices}"
ItemTemplate="{StaticResource CategoryTemplate}" x:Name="FromTreeView" />
The classes that are bound are:
public class AccountAtAddress
{
public string AccountNumber { get; set; }
}
public class ServiceCategory
{
public string CategoryName { get; set; }
public ObservableCollection<AccountAtAddress> CategoryServices;
}
The problem that I have is that the first level items show but no children. Does anyone have ideas for how to make this work?
You need to make CategoryServices into a public property. You have it defined as a public field and the databinding mechanism doesn't work with fields.
Try the following instead:
<Grid.Resources>
<sdk:HierarchicalDataTemplate DataType="AccountAtAddress">
<TextBlock Text="{Binding AccountNumber}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate DataType="ServiceCategory"
ItemsSource="{Binding CategoryServices}">
<TextBlock Text="{Binding Path=CategoryName}" FontWeight="Bold" />
</sdk:HierarchicalDataTemplate>
</Grid.Resources>