I have an ItemsControl in which I display different properties and values, with the name on one side and a TextBox on the other side. The ItemsSource is a collection of objects of a custom class, that has Name, Value and PropertyType properties (using reflections propertyinfo)
Now I would like to improve this by being able to detect whether the property is of type bool for example, which would display a checkbox instead of a textbox. Is this possible using a DataTrigger?
I got it semi-working using a Control of which I set the template to a textbox or checkbox according to the type, but when I try to "tab" to the next textbox or checkbox, it focuses the control that has the textbox/checkbox first, and only after another "tab" it focuses the containing textbox/checkbox/..
So if anybody know a solution for this, that would be greatly appreciated!
Use the solution you already have and set the Focusable property to false on the control that wrongly gets tab focus.
You can use DataTemplate to select different View based on Value property type.
View:
<ItemsControl ItemsSource="{Binding Path=Options}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type System:Boolean}">
<CheckBox IsChecked="{Binding Path=.}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type System:String}">
<TextBox Text="{Binding Path=.}"/>
</DataTemplate>
</DataTemplate.Resources>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name, Mode=OneWay}"/>
<ContentControl Content="{Binding Path=Value}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
ViewModel:
public class MainViewModel
{
public ArrayList Options { get; set; }
public MainViewModel()
{
Options = new ArrayList();
Options.Add(new TextProperty());
Options.Add(new BoolProperty());
}
}
public class TextProperty
{
public string Name { get; set; }
public string Value { get; set; }
public TextProperty()
{
Name = "Name";
Value = "Default";
}
}
public class BoolProperty
{
public string Name { get; set; }
public bool Value { get; set; }
public BoolProperty()
{
Name = "IsEnabled";
Value = true;
}
}
Related
I have a List of objects of type MenuModel called MenuList inside my ViewModel. I am using CaliburnMicro framework
I would like to show this list as a list of ToggleButtons that have IsChecked property bound to other object list called SelectedMenusMonday, which is list of type SelectedMenuModel that has only IsSelected property and is the same length as MenuList.
MenuModel looks like this:
public class MenuModel
{
public int MenuKey { get; set; }
public string MenuName { get; set; }
public string Description { get; set; }
}
MenuList:
public List<MenuModel> MenuList
{
get { return _MenuList; }
set => Set(ref _MenuList, value);
}
SelectedMenuModel
public class SelectedMenuModel
{
public bool IsSelected { get; set; }
}
And SelectedMenusMonday list:
private BindableCollection<SelectedMenuModel> _SelectedMenusMonday = new BindableCollection<SelectedMenuModel>();
public BindableCollection<SelectedMenuModel> SelectedMenusMonday
{
get { return _SelectedMenusMonday; }
set => Set(ref _SelectedMenusMonday, value);
}
I am trying to display like this:
<ItemsControl x:Name="MondayMenuList" ItemsSource="{Binding MenuList}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Content="{Binding MenuName}" IsChecked="{Binding Path=DataContext.SelectedMenusMonday.IsSelected, RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The MenuList and SelectedMenus Monday get filled from SQL DB. This is the solution i tried, but it does not work. Can someone help me please! I want the ToggleButtons to be "checked" if the item on the SelectedMenusMonday have IsSelected property as true.
Thank you very much!
Name the root element in your view (or wherever you know the DataContext to be correct) and use ElementName binding as shown here:
<UserControl x:Name="view">
<Grid>
<ItemsControl x:Name="MondayMenuList" ItemsSource="{Binding MenuList}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Content="{Binding MenuName}" IsChecked="{Binding ElementName=view, Path=DataContext.SelectedMenusMonday}">
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
Note the x:Name="view" in the UserControl element.
I'm looking for a simple way to sort items of ItemsControl based on a property specified in implicit DataTemplate for the items to which the control is bound. And defining the properties on DataTemplate is crucial here, because I cannot add the sorting property on the item itself.
So, for the below example VM layer:
public interface INamed
{
string Name { get; set; }
}
public class FirstModel : INamed
{
public string Name { get; set; }
}
public class SecondModel : INamed
{
public string Name { get; set; }
}
public class ViewModel
{
public ViewModel()
{
Models = new INamed[] { new SecondModel {Name = "Second"}, new FirstModel {Name = "First"}};
}
public IEnumerable<INamed> Models { get; private set; }
}
and this attached property:
public static class AttachedProperties
{
public static int GetSortOrder(DependencyObject obj)
{
return (int)obj.GetValue(SortOrderProperty);
}
public static void SetSortOrder(DependencyObject obj, int value)
{
obj.SetValue(SortOrderProperty, value);
}
public static readonly DependencyProperty SortOrderProperty =
DependencyProperty.RegisterAttached("SortOrder", typeof(int), typeof(AttachedProperties), new PropertyMetadata(0));
}
I have the following DataTemplate definitions (over-simplified):
<DataTemplate DataType="{x:Type local:FirstModel}">
<StackPanel Background="Red" local:AttachedProperties.SortOrder="1">
<Label>First's Name:</Label>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SecondModel}">
<StackPanel Background="Green" local:AttachedProperties.SortOrder="2">
<Label>Second's Name:</Label>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
Somewhere the usage will be like:
<ItemsControl ItemsSource="{Binding Models}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
And here the order of the items should be based on the attached property I defined for the data templates. Don't see any option to use the CollectionViewSource directly here, may be I'm wrong...
Current options I see, none too appealing, are:
Attached behavior on the ItemsControl, traversing the visual tree of each new item and sorting the Items in accordance with the found SortOrder value
A custom ItemsControl with it's own sorting logic, panel, blackjack and... you know
Wrapping the model instances in some kind of proxy with SortOrder property on it. Which still requires some custom/user control code-behind or ViewModel class changes
Is there some better/easier way I miss?
I guess you cant
I think the only way is to implement your own ItemsControl
Or wrap the models with another class
Maybe this helps:
SortDescription with custom attached property
I have a bound property on a class, Foo which is defined similar to as follows (edited for clarity),
public class Foo : INotifyPropertyChanged
{
public Foo()
{
// This should notify when IsHidden changes?
MyApp.ViewModel.HiddenCategories.CollectionChanged += (s, e) => {
this.NotifyPropertyChanged("IsHidden");
};
}
public CategoryId Category { get; set; }
// IsHidden depends on a `global' ObservableCollection object on the ViewModel
public bool IsHidden
{
get { return MyApp.ViewModel.HiddenCategories.Contains(this.Category); }
}
// IsHidden is toggled by adjusting the global ObservableCollection - how to notify the UI?
public void ToggleHidden()
{
if (this.IsHidden)
MyApp.ViewModel.HiddenCategories.Remove(this.Category);
else
MyApp.ViewModel.HiddenCategories.Add(this.Category);
}
#region INotifyPropertyChanged members
...
}
The ViewModel has the following defined on it,
public class FooRegion
{
public string RegionName { get; set; }
// Foos is bound in the top ListBox DataTemplate
// Each Foo has properties bound in the sub ListBox DataTemplate
public ObservableCollection<Foo> Foos { get; set; }
}
// This is actually what is bound to the top level ListBox
public ObservableCollection<FooRegion> FoosByRegion { get; set; }
public ObservableCollection<CategoryId> HiddenCategories { get; set; }
My XAML defines two ItemTemplates in the resources as follows,
<phone:PhoneApplicationPage.Resources>
...
<DataTemplate x:Key="MainItemTemplate">
<StackPanel >
<TextBlock Text="{Binding Name}"/>
<ListBox ItemsSource="{Binding Foos}"
ItemTemplate="{StaticResource SubItemTemplate}" />
</StackPanel>
</DataTemplate>
...
<DataTemplate x:Key="SubItemTemplate">
<StackPanel Opacity="{Binding IsHidden, Converter={StaticResource BoolToOpacity}}" >
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="{Binding IsHidden, ConverterParameter=unhide foo|hide foo,
Converter={StaticResource BoolToStrings}}" Tap="toggleHideFooContextMenuItem_Tap" />
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<TextBlock Text="Some Text Here"/>
</StackPanel>
</DataTemplate>
...
</phone:PhoneApplicationPage.Resources>
These resources are called on to a 'nested' ListBox as follows,
<ListBox ItemTemplate="{StaticResource MainItemTemplate}" ItemsSource="{Binding FoosByRegion}" />
This method appears to only work piecemeal, Some Foo objects are updated in the UI, but others are not - as if the notification is not reaching the UI.
How should I be tackling this problem?
ContextMenu from the Windows Phone Toolkit applies an animation which affects the opacity of the surrounding elements. Applying the opacity to the child elements individually solved the problem.
I have placed a Radio button in a listbox content and binded it with list of QuizOption1
binding is working fine and showing the radio button checked if the property IsSelected is passed as true. the class definition is given below.
class QuizOption1
{
public int QuizID { get; set; }
public int QuizOptionID { get; set; }
public string Description { get; set; }
public bool IsSelected { get; set; }
}
While checking for the checked items, i am using following code
var lstItems = (List<QuizOption1>)lst.ItemsSource;
var selItems = lstItems.Where(op => op.IsSelected == true).FirstOrDefault();
The binding is as follows.
<ListBox Name="lst1" Grid.Row="1" >
<ListBox.ItemTemplate >
<DataTemplate >
<RadioButton
Foreground="#333333"
Background="#ffededed"
Tag="{Binding QuizOptionID}"
Content="{Binding Description}"
IsEnabled="True"
GroupName="{Binding QuizID}"
IsChecked="{Binding Path=IsSelected}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
but selItems is always null. Can anyone tell me why? Thanks in advance.
The answer is very simple, i need to add the Mode=TwoWay attribute in binding and binding looks like following.
Thanks anyways.
IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
Current Setup
I have a custom class representing an installer file and some properties about that file, conforming to the following interface
public interface IInstallerObject
{
string FileName { get; set; }
string FileExtension { get; set; }
string Path { get; set; }
int Build { get; set; }
ProductType ProductType { get; set; }
Architecture ArchType { get; set; }
bool Configurable { get; set; }
int AverageInstallTime { get; set; }
bool IsSelected { get; set; }
}
My ViewModel has a ReadOnlyObservableCollection<IInstallerObject> property named AvailableInstallerObjects.
My View has a GroupBox containing the ItemsControl which binds to the aforementioned property.
<GroupBox Header="Products">
<ItemsControl ItemsSource="{Binding Path=AvailableInstallerObjects}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=FileName}" Margin="5" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
The binding works correctly, except it's not user friendly. 100+ items are shown.
Need Help Here
I'd like to be able to use my collection of IInstallerObjects but have the View present them with the following ItemTemplate structure.
<GroupBox Header="Products">
<ItemsControl ItemsSource="{Binding Path=AvailableInstallerObjects}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=ProductType}" Margin="5" />
<ComboBox ItemsSource="{Binding Path=Build}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
Basically I want to be able to group by the ProductType property, showing a list of the available products, with the ComboBox representing the available Build property values for IInstallerObjects of the ProductType.
I can use LINQ in the ViewModel to extract the groupings, but I have no idea how I'd bind to what I've extracted.
My research also turned up the possibility of using a CollectionViewSource but I'm not certain on how I can apply that to my current setup.
I appreciate your help in advance. I'm willing to learn so if I've overlooked something obvious please direct me to the information and I'll gladly educate myself.
If Build should be a collection type.
so your class should be structured like this as an example.
Public Class Customer
Public Property FirstName as string
Public Property LastName as string
Public Property CustomerOrders as observableCollection(OF Orders)
End Class
This should give you the expected results. Each item in the main items presenter will show first name last name and combobox bound to that customers orders.
I know it's simple but this should do.
All you have to do is declare a CollectionViewSource in your view and bind it to the ObservableCollection. Within this object you declare one or more GroupDescriptions which will split up the source into several groups.
Bind this source to the listbox, create a Template for the group description and you are done.
An example can be found here: WPF Sample Series – ListBox Grouping, Sorting, Subtotals and Collapsible Regions. More about CollectionViewSource can be found here: WPF’s CollectionViewSource
The description of your problem lead me to believe you are looking for some kind of colapsing / expanding / grouped / tree-view sort of thing.
XAML for the tree-view
<Window x:Class="WPFLab12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:WPFLab12"
Title="MainWindow" Height="350" Width="525">
<Grid>
<GroupBox Header="Products">
<TreeView ItemsSource="{Binding Path=ProductTypes}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type loc:ProductType}"
ItemsSource="{Binding AvailableInstallerObjects}">
<TextBlock Text="{Binding Description}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:InstallerObject}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=FileName}" Margin="5" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</GroupBox>
</Grid>
</Window>
What does that do? Well, it establishes a hierarchy of controls in the tree based on the type of data found. The first HierarchicalDataTemplate handles how to display the data for each class, and how they are related in the hierarchy. The second HierarchicalDataTemplate handles how to display each InstallerObject.
Code behind for the Main Window:
public partial class MainWindow : Window
{
public ReadOnlyObservableCollection<ProductType> ProductTypes
{
get { return (ReadOnlyObservableCollection<ProductType>)GetValue(ProductTypesProperty); }
set { SetValue(ProductTypesProperty, value); }
}
// Using a DependencyProperty as the backing store for ProductTypes. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ProductTypesProperty =
DependencyProperty.Register("ProductTypes", typeof(ReadOnlyObservableCollection<ProductType>), typeof(MainWindow), new UIPropertyMetadata(null));
public MainWindow()
{
this.InitializeComponent();
this.ProductTypes = new ReadOnlyObservableCollection<ProductType>(
new ObservableCollection<ProductType>()
{
new ProductType()
{
Description = "Type A",
AvailableInstallerObjects = new ReadOnlyObservableCollection<InstallerObject>(
new ObservableCollection<InstallerObject>()
{
new InstallerObject() { FileName = "A" },
new InstallerObject() { FileName = "B" },
new InstallerObject() { FileName = "C" },
})
},
new ProductType()
{
Description = "Type B",
AvailableInstallerObjects = new ReadOnlyObservableCollection<InstallerObject>(
new ObservableCollection<InstallerObject>()
{
new InstallerObject() { FileName = "A" },
new InstallerObject() { FileName = "D" },
})
}
});
this.DataContext = this;
}
}
This is totally cheating, though - normally the MainWindow.cs would not serve as the DataContext and have all this stuff. But for this example I just had it make a list of ProductTypes and populate each ProductType class with the InstallerObject instances.
Classes I used, note I made some assumptions and modified your class to suit this View Model better:
public class InstallerObject
{
public string FileName { get; set; }
public string FileExtension { get; set; }
public string Path { get; set; }
public int Build { get; set; }
public bool Configurable { get; set; }
public int AverageInstallTime { get; set; }
public bool IsSelected { get; set; }
}
public class ProductType
{
public string Description { get; set; }
public ReadOnlyObservableCollection<InstallerObject> AvailableInstallerObjects
{
get;
set;
}
public override string ToString()
{
return this.Description;
}
}
So, in MVVM, it seems to me that your current InstallerObject class is more of a Model layer sort of thing. You might consider transforming it in your ViewModel to a set of collection classes that are easier to manage in your View. The idea in the ViewModel is to model things similarly to how they are going to be viewed and interracted with. Transform your flat list of InstallerObjects to a new collection of hierarchical data for easier binding to the View.
More info on various ways to use and customize your TreeView: http://www.codeproject.com/Articles/124644/Basic-Understanding-of-Tree-View-in-WPF