Change XAML to c# code - c#

I am trying to change a data template of buttons from WPF XAML to c# code I.E. I want to create the buttons programatically in a .cs file.
The XAML looks like this:
<ItemsControl DataContext="{Binding Source={StaticResource Things}}" ItemsSource="{Binding}" Name="buttons" Margin="0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local:FishEyePanel ScaleToFit="false"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Command="Open" Tag="{Binding XPath=#Tag, Path=Value}" Margin="5" Padding="5" HorizontalContentAlignment="Center" Width="{Binding XPath=#Width}" Height="{Binding XPath=#Height}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
The line:
<Button Command="Open" Tag="{Binding XPath=#Tag, Path=Value}" Margin="5" Padding="5" HorizontalContentAlignment="Center" Width="{Binding XPath=#Width}" Height="{Binding XPath=#Height}"/>
is where I bind all the buttons to a xml file, here is the file:
<XmlDataProvider x:Key="Things" XPath="Things/Thing">
<x:XData>
<Things xmlns="">
<Thing Button="uno1" Tag="abc" Width="200" Height="200"/>
</Things>
</x:XData>
</XmlDataProvider>
But what I want is instead of calling the xml file with all the bottoms predefined, call a method in cs with the buttons, something like this:
public void createButtons()
{
Button button = new Button();
button.Tag = "I am from cs";
buttons.Items.Add(button);
}
Is this possible?

You don't need C# to have dynamic buttons per se.
I have an app in production that has dynamic buttons (you can have different buttons add more, etc, all in code and they are just displayed in the WPF UI)
You just need to create a ButtonViewModel object and then put a list of ButtonViewModel in your code.
using System;
using System.Windows.Input;
using Setup.Common;
namespace ExampleOfButtonList
{
public class ButtonViewModel : ViewModelBase
{
#region Member fields
protected bool _IsVisible;
protected bool _IsEnabled;
protected RelayCommand _ButtonCommand;
protected String _Text;
#endregion
#region Constructors
/// <summary>
/// The default constructor
/// </summary>
public ButtonViewModel()
{
}
#endregion
#region Properties
public virtual ICommand ButtonCommand
{
get { return _ButtonCommand; }
set
{
_ButtonCommand = value as RelayCommand;
NotifyPropertyChanged("ButtonCommand");
}
}
public bool IsVisible
{
get { return _IsVisible; }
set
{
_IsVisible = value;
NotifyPropertyChanged("IsVisible");
}
}
public String Text
{
get { return _Text; }
set
{
_Text = value;
NotifyPropertyChanged("Text");
}
}
#endregion
}
}
Create the list like this in your code:
List<ButtonViewModel> buttonList = new List<ButtonViewModel>();
ButtonViewModel bvm1 = new ButtonViewModel();
bvm.ButtonCommand = new RelayCommand(f => SomeMethod());
buttonList.add(bvm1);
Note: You can get RelayCommand from any MVVM framework or here: MVVM Base.zip
Then use an ItemsControl and bind its ItemsSource to your buttonList and it will properly display the list.

Related

Check ckeckbox in wpf by clicking on the whole item

My client has got the problem with the size of the CheckBox inside a ListBox. I agree, it's small and not so easy to check at times.
I've tried to find a way to make a CheckBox bigger but I've found out that it's complicated (and would require using Blend, which I don't want to use).
What I want to do though is to check the CheckBox when clicking on a whole item.
[ ] some text
In this example - on "some text" or inside the item wherever. Right now I have to click inside the CheckBox to have it checked.
I generate my CheckBoxes dynamically.
My xamls of this control looks like this:
<ListBox Name="restoredDBsListBox" ItemsSource="{Binding ProperlyRestoredDatabases}" Grid.ColumnSpan="2" HorizontalAlignment="Left" Height="170" Margin="34,160,0,0" VerticalAlignment="Top" Width="276" SelectionMode="Extended">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox Name="check" IsChecked="{Binding IsChecked, Mode=TwoWay}" Margin="3" VerticalAlignment="Center"/>
<ContentPresenter Content="{Binding Value}" Margin="1"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and my ViewModel:
/// <summary>
/// Defines the _properlyRestoredDatabases
/// </summary>
private CheckableObservableCollection<string> _properlyRestoredDatabases;
/// <summary>
/// Gets or sets the ProperlyRestoredDatabases
/// </summary>
public CheckableObservableCollection<string> ProperlyRestoredDatabases
{
get { return _properlyRestoredDatabases; }
set
{
_properlyRestoredDatabases = value;
OnPropertyChanged("ProperlyRestoredDatabases");
}
}
CheckableObservableCollection class :
public class CheckableObservableCollection<T> : ObservableCollection<CheckedWrapper<T>>
{
private ListCollectionView _selected;
public CheckableObservableCollection()
{
_selected = new ListCollectionView(this);
_selected.Filter = delegate (object checkObject) {
return ((CheckedWrapper<T>)checkObject).IsChecked;
};
}
public void Add(T item)
{
this.Add(new CheckedWrapper<T>(this) { Value = item });
}
public ICollectionView CheckedItems
{
get { return _selected; }
}
internal void Refresh()
{
_selected.Refresh();
}
}
and CheckedWrapper
public class CheckedWrapper<T> : INotifyPropertyChanged
{
private readonly CheckableObservableCollection<T> _parent;
public CheckedWrapper(CheckableObservableCollection<T> parent)
{
_parent = parent;
}
private T _value;
public T Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged("Value");
}
}
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
CheckChanged();
OnPropertyChanged("IsChecked");
}
}
private void CheckChanged()
{
_parent.Refresh();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler pceh = PropertyChanged;
if (pceh != null)
{
pceh(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The CheckBox has a Content property so there is no reason to use a separate ContentPresenter. If you also add an ItemContainerStyle that stretches the ListBoxItem container horizontally, you will be able to check and uncheck the CheckBox by clicking anywhere on the row:
<ListBox Name="restoredDBsListBox" ItemsSource="{Binding ProperlyRestoredDatabases}" Grid.ColumnSpan="2" HorizontalAlignment="Left" Height="170" Margin="34,160,0,0" VerticalAlignment="Top" Width="276" SelectionMode="Extended">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Name="check" IsChecked="{Binding IsChecked, Mode=TwoWay}"
Content="{Binding Value}"
Margin="3"
VerticalAlignment="Center"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

How to properly bind multiple ViewModels when using DataTemplateSelector [duplicate]

This question already has answers here:
How to use DataTemplateSelector with ContentControl to display different controls based on the view-model?
(2 answers)
Closed 4 years ago.
I'm trying to write a simple dialog that would accept a value in a SpinEdit or a text in a TextEdit. I'm using multiple VMs and I made a selector that should view a proper control based on the logic in the c++/cli file.
XAML:
xmlns:local="clr-namespace:asd"
Title="{Binding Path=Title, Mode=OneTime}"
<dx:DXWindow.Resources>
<DataTemplate x:Key="TInputValueVM" DataType="{x:Type local:TInputValueVM}">
<dxe:SpinEdit Height="23" Width="200"
Text="{Binding Value, Mode=TwoWay}"
Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}" />
</DataTemplate>
<DataTemplate x:Key="TInputTextVM" DataType="{x:Type local:TInputTextVM}">
<dxe:TextEdit Height="23" Width="200"
Text="{Binding Value, Mode=TwoWay}"
MaskType="RegEx" Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}"/>
</DataTemplate>
<local:PropertyDataTemplateSelector x:Key="templateSelector"
DataTemplate_Value="{StaticResource TInputValueVM}"
DataTemplate_Text="{StaticResource TInputTextVM}" />
</dx:DXWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" >
<Label x:Uid="Label" MinHeight="24" MinWidth="60" Content="Value" />
<ContentControl Content="{Binding Path=Whoami}" ContentTemplateSelector="{StaticResource templateSelector}" />
</StackPanel>
<StackPanel Grid.Row="1" x:Uid="OKCancel_Buttons" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Button Height="23" x:Name="OK_Button" Click="OK_Click" Content="OK" IsDefault="True" HorizontalAlignment="Right" MinWidth="95" />
<Button Height="23" x:Name="Cancel_Button" Click="Cancel_Click" Content="Cancel" HorizontalAlignment="Right" MinWidth="95" />
</StackPanel>
</Grid>
In c# I have a base VM and two VMS that extend it, one for values and one for text. The rest of the properties stay the same.
C#
namespace asd
{
public class TInputBaseVM : ViewModelBase
{
private string m_sTitle;
private string m_sMask;
private int m_nInputLenght;
private string m_sWhoami;
public TInputBaseVM(string A_sTitle, string A_sMask, int A_nInputLength)
{
m_sTitle = A_sTitle;
m_sMask = A_sMask;
m_nInputLenght = A_nInputLength;
}
protected string Title
{
get { return m_sTitle; }
set { SetProperty(ref m_sTitle, value, () => Title); }
}
protected string Mask
{
get { return m_sMask; }
set { SetProperty(ref m_sMask, value, () => Mask); }
}
protected int InputLength
{
get { return m_nInputLenght; }
set { SetProperty(ref m_nInputLenght, value, () => InputLength); }
}
protected string Whoami
{
get { return m_sWhoami; }
set { SetProperty(ref m_sWhoami, value, () => Whoami); }
}
}
public class TInputValueVM : TInputBaseVM
{
public TInputValueVM(string A_sTitle, string A_sMask, int A_nInputLength, double A_nValue) : base(A_sTitle, A_sMask, A_nInputLength)
{
Value = A_nValue;
Whoami = "Value";
}
private double m_nValue;
public double Value
{
get { return m_nValue; }
set { SetProperty(ref m_nValue, value, () => Value); }
}
}
public class TInputTextVM : TInputBaseVM
{
public TInputTextVM(string A_sTitle, string A_sMask, int A_nInputLength, string A_sValue) : base(A_sTitle, A_sMask, A_nInputLength)
{
Value = A_sValue;
Whoami = "Text";
}
private string m_sValue;
public string Value
{
get { return m_sValue; }
set { SetProperty(ref m_sValue, value, () => Value); }
}
}
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DataTemplate_Value { get; set; }
public DataTemplate DataTemplate_Text { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var selector = item as string;
if(selector == "Value")
return DataTemplate_Value;
return DataTemplate_Text;
}
}
}
In c++/cli I create an object of a proper VM and I'd like the WPF to automatically update the view to either spinedit or textedit, however I'm not sure how to properly bind the properties from the C#. If I explicitly type 'Value' in the Content property of the ContentControl then it displays the spinEdit but I don't know how to bind it so it automatically takes the correct property.
EDIT: I'm adding c++/cli code to show how I choose different VMs
C++/cli:
bool TSignalNumberPositionDialogCLR::StartDialog(TSignalNumberPositionSupport& A_Attributes, HWND A_hwndParent, LPTSTR String)
{
try
{
TInputValueVM ^oExchange_Value;
TInputTextVM ^oExchange_Text;
int inputFormat = A_Attributes.GetInputFormat();
if(inputFormat)
oExchange_Text = gcnew TInputTextVM(gcnew System::String(A_Attributes.GetTitle()), gcnew System::String(A_Attributes.GetMask()),
A_Attributes.GetInputLength(), gcnew System::String(A_Attributes.GetInitialText()));
else
oExchange_Value = gcnew TInputValueVM(gcnew System::String(A_Attributes.GetTitle()), gcnew System::String(A_Attributes.GetMask()),
A_Attributes.GetInputLength(), A_Attributes.GetInitialValue());
Dialogs::TSignalNumberPositionDialog^ dialog = gcnew Dialogs::TSignalNumberPositionDialog();
if(inputFormat)
dialog->DataContext = oExchange_Text;
else
dialog->DataContext = oExchange_Value;
dialog->ShowDialog();
if(dialog->DialogResult)
{
CString nValue;
if(inputFormat)
nValue = oExchange_Text->Value;
else
nValue = ((Decimal)oExchange_Value->Value).ToString("F2", CultureInfo::InvariantCulture);
A_Attributes.UpdateValue(nValue, String, A_Attributes.GetInputLength());
return true;
}
return false;
}
catch(Exception^ e)
{
e;
}
}
based on the 'inputFormat' variable I want to display different controls in the dialog.
EDIT: Based on #Clemens comments I got rid of the selector sectionand the x:Key property in the DataTemplates. I changed the content opf the Content property to Content="{Binding}" and it somehow works. The moment I create a VM it selects the correct one.
Based on your comment. Let me improve my answer. As you are facing issue in VM selection. so plesae concentrate how I assigned VM to datatemplate. Although it is done in very basic way, you can handle it if you you are using MVVM packages.
I have created 2 data template and 2 vms and each vm is bound to datatemplate. To verify, I have a combobox, which will select datatemplate based on selected value.
Here is Sample VM
public class VM : System.ComponentModel.INotifyPropertyChanged
{
private string title;
private SolidColorBrush background;
public string Title { get => title; set { title = value; RaisePropertyChanged(); } }
public SolidColorBrush Background { get => background; set { background = value; RaisePropertyChanged(); } }
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class VM1: VM
{
public VM1()
{
Title = "This is VM1";
Background = Brushes.Yellow;
}
}
public class VM2: VM
{
public VM2()
{
Title = "This is VM2";
Background = Brushes.Orange;
}
}
Now check for resources
<local:VM1 x:Key="VM1"/>
<local:VM2 x:Key="VM2"/>
<DataTemplate x:Key="DT1">
<Grid DataContext="{StaticResource VM1}">
<TextBlock Text="{Binding Title}" Background="{Binding Background}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="DT2">
<Grid DataContext="{StaticResource VM2}">
<TextBlock Text="{Binding Title}" Background="{Binding Background}"/>
</Grid>
</DataTemplate>
<Style TargetType="ContentControl" x:Key="contentStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cmbo, Path=SelectedValue}" Value="Template1">
<Setter Property="ContentTemplate" Value="{StaticResource DT1}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=cmbo, Path=SelectedValue}" Value="Template2">
<Setter Property="ContentTemplate" Value="{StaticResource DT2}" />
</DataTrigger>
</Style.Triggers>
</Style>
and finally I have combobox and content control just to verify
<ComboBox Name="cmbo"/>
<ContentControl Style="{StaticResource contentStyle}"/>
where cmbo.ItemsSource = new List { "Template1", "Template2" };
Hope you got the point

WPF Window hide Content DependencyProperty

I want to create a Window which redeclares it's own DependencyProperty named Content.
public partial class InfoWindow : Window
{
public static new readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(InfoWindow), new PropertyMetadata(null));
public object Content
{
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
}
And XAML bind this property
<ContentControl Content="{Binding ElementName=_this, Path=Content}" />
It works fine, just the Visual Studio Designer complains Logical tree depth exceeded while traversing the tree. This could indicate a cycle in the tree.
Is there any way how to tell the Designer that binding is to the InfoWindow.Content and not Window.Content? Or is it a bad idea hide the property and should I renamed my property?
What I am trying to achieve here is the idea of dynamically defining the Buttons that are used to bring up different views for navigating to different forms. (See below: )
The link between the View and View Models are setup inside the Dictionary View_ViewModel which is used to identify the view to set for the Current view when the button is pressed.
(Note: I have tried to use the most basic objects avoiding IOC containers and such like, so as to make it easier to understand the code)
The most important thing to remember is to set the DataContext correctly otherwise you will get the Logical tree depth exceeded while traversing the tree. Error. You could either do this in the Code behind of the View or inside the XAML.
Example:
public partial class SetupForm : UserControl
{
public SetupForm()
{
InitializeComponent();
DataContext = new SetupFormVM();
}
}
OR
<UserControl.DataContext>
<SaleVM:SalesEntryVM />
</UserControl.DataContext>
Here is a code snippet which probably explains it more clearly and probably answers your question.
The view model defines the how many buttons and views you want in the Main Window. This is achieved by having ItemsControl binding to a list in View Model class.
The Button command is bounded to ICommand ChangeViewCommand property in the View Model class which evaluates the Button pressed and .calls ViewChange method which changes the CurrentView (this is bound to the Content in the XAML)
This is what my main Window xaml looks like.
<Window x:Class="MyNameSpace.Views.ApplicationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyNameSpace.Views"
mc:Ignorable="d"
Title="ApplicationWindow" Height="Auto" Width="Auto">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition /> -------------------> repeated five times
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition /> -------------------------> repeated five times
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" >
<!-- Bind to List of Pages -->
<ItemsControl ItemsSource="{Binding ControlItemsNamesList}" DockPanel.Dock="Top" >
<!-- Stack the buttons horizontally --> The list contains the labels to assign to the buttons
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate> ---------------------------------------> This to stack the buttons Horizontally
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- This looks at the list items and creates a button with ControlName -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding ControlName}"
Command="{Binding DataContext.ChangeViewCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" ------> This is important for the Buttons to work Window or ContentControl.
CommandParameter="{Binding }"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
<ContentControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="5" Grid.RowSpan="4" Content="{Binding CurrentView}"/> ---------> This is where I want the new Windows to appear when I click the button
</Grid>
This is what one of my User control xaml looks like that will appear when I click the button in the Main Window.
<UserControl x:Class="MyNameSpace.Views.SetupForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition /> ------------------- repeated five times
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/> ------------------- repeated five times
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Grid.Column="0" Grid.RowSpan="5" Background="AliceBlue" Margin="0,0,0,0" >
<!-- Bind to List of Pages -->
<ItemsControl ItemsSource="{Binding ControlItemsNamesList}" DockPanel.Dock="Left" >
<!-- Stack the buttons horizontally -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- This looks at the list items and creates a button with ControlName -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding ControlName}"
Command="{Binding DataContext.ChangeViewCommand, RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}"
CommandParameter="{Binding }"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
<ContentControl Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="5" Grid.RowSpan="4" Content="{Binding CurrentView}"/>
</Grid>
</UserControl>
This is the ViewModel for the Main window:
using Products.MVVMLibrary;
using Products.MVVMLibrary.Interfaces;
using MyNameSpace.Views;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Input;
namespace MyNameSpace.ViewModel
{
public class ApplicationVM : ObservableObject
{
private MyNameSpace IControlItem currentNavigationItem;
private MyNameSpace ContentControl currentView;
private MyNameSpace List<IControlItem> NavigationList;
private MyNameSpace ICommand changeViewCommand;
private MyNameSpace ViewConverter viewDictionary;
private MyNameSpace Dictionary<string, LinkViewToViewModel> View_ViewModel;
public ApplicationVM()
{
viewDictionary = new ViewConverter();
View_ViewModel = new Dictionary<string, LinkViewToViewModel>();
NavigationList = new List<IControlItem>();
InitialiseLists();
}
private MyNameSpace void AddControlNavigationItems(string name, ContentControl view, ObservableObject viewModel)
{
View_ViewModel.Add(name, new LinkViewToViewModel(view, viewModel));
IControlItem item = (IControlItem)viewModel;
NavigationList.Add(item);
}
private MyNameSpace void InitialiseLists()
{
AddControlNavigationItems("Sales", new SalesForm(), new SalesEntryVM());
AddControlNavigationItems("Purchases", new PurchaseEntryForm(), new PurchasesVM());
AddControlNavigationItems("Setup", new SetupForm(), new SetupFormVM());
//Use the property instead which creates the instance and triggers property change
CurrentViewModel = (IControlItem)View_ViewModel[View_ViewModel.Keys.ElementAt(0)].ViewModel;
CurrentView = View_ViewModel[View_ViewModel.Keys.ElementAt(0)].View;
}
public List<IControlItem> ControlItemsNamesList
{
get => NavigationList;
}
/// <summary>
/// Provides a list of names for Navigation controls to the control item
/// </summary>
public Dictionary<string, LinkViewToViewModel> ApplicationViews
{
get
{
return View_ViewModel;
}
set
{
View_ViewModel = value;
}
}
public ContentControl CurrentView
{
get
{
return currentView;
}
set
{
currentView = value;
OnPropertyChanged("CurrentView");
}
}
public IControlItem CurrentViewModel
{
get
{
return currentNavigationItem;
}
set
{
if (currentNavigationItem != value)
{
currentNavigationItem = value;
OnPropertyChanged("CurrentViewModel");
}
}
}
/// <summary>
/// This property is bound to Button Command in XAML.
/// Calls ChangeViewModel which sets the CurrentViewModel
/// </summary>
public ICommand ChangeViewCommand
{
get
{
if (changeViewCommand == null)
{
changeViewCommand = new ButtonClick(
p => ViewChange((IControlItem)p), CanExecute);
}
return changeViewCommand;
}
}
#region Methods
private MyNameSpace void ViewChange(IControlItem viewname)
{
foreach (KeyValuePair<string, LinkViewToViewModel> item in View_ViewModel)
{
if (item.Key == viewname.ControlName)
{//Set the properties of View and ViewModel so they fire PropertyChange event
CurrentViewModel = (IControlItem)item.Value.ViewModel;
CurrentView = item.Value.View;
break;
}
}
}
private MyNameSpace bool CanExecute()
{
return true;
}
#endregion
}
}
Other classes
public class LinkViewToViewModel
{
public LinkViewToViewModel(ContentControl view, ObservableObject viewModel)
{
View = view;
ViewModel = viewModel;
}
public ContentControl View { get; set; }
public ObservableObject ViewModel { get; set; }
}

WPF Customcontrol, Templates, Inheritance and Dependencyproperties

I've got some troubles with a custom control I need to create. I try to explain you my needs first
I need to have a combobox that permits to check more than one item at time (with checkbox) but I want it to be smart enought to bind to a specific type.
I've found some MultiSelectionComboBox but none reflects my need.
Btw my main problem is that I wish to have a generic class as
public class BaseClass<T> : BaseClass
{
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable<T>), typeof(BaseClass<T>), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(BaseClass<T>.OnItemsSourceChanged)));
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
int i = 0;
//MultiSelectComboBox control = (MultiSelectComboBox)d;
//control.DisplayInControl();
}
public IEnumerable<T> ItemsSource
{
get { return (IEnumerable<T>)GetValue(ItemsSourceProperty); }
set
{
SetValue(ItemsSourceProperty, value);
}
}
}
public class BaseClass : Control
{
}
and a more context specific item for example
public class MultiCurr : BaseClass<Currency>
{
static MultiCurr()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiCurr), new FrameworkPropertyMetadata(typeof(MultiCurr)));
}
}
In my App.xaml I've defined a resource as
<ResourceDictionary>
<Style TargetType="local:MultiCurr">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MultiCurr">
<ComboBox Width="120" Background="Red" Height="30" ItemsSource="{Binding ItemsSource}" DisplayMemberPath="Description" ></ComboBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
In my MainWindow I've created an object as
<Grid>
<local:MultiCurr x:Name="test" ItemsSource="{Binding Currencies}"></local:MultiCurr>
</Grid>
and the MainWindow.cs is defined as
public partial class MainWindow : Window, INotifyPropertyChanged
{
private IList currencies;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var lst = new List<Currency>();
for (int i = 0; i < 10; i++)
{
var curr = new Currency
{
ID = i,
Description = string.Format("Currency_{0}", i)
};
lst.Add(curr);
}
Currencies = lst;
}
public IList<Currency> Currencies
{
get
{
return this.currencies;
}
set
{
this.currencies = value;
NotifyPropertyChanged("Currencies");
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And here's the result ...
I was wondering what am I doing wrong? is it possible what am I tring to achieve?
Thanks
UPDATE #1:
I've seen that the main problem is the datacontext of the custom usercontrol
<Application.Resources>
<ResourceDictionary>
<Style TargetType="local:MultiCurr">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MultiCurr">
<ComboBox Width="120" Background="Red" Height="30" ItemsSource="{Binding **Currencies**}" DisplayMemberPath="{Binding **DisplayMemeberPath**}" ></ComboBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
If I put ItemsSource as Currency (which is a property of the MainWindow) it shows.
If I put ItemsSource and DisplayMemberPath (which are defined in the BaseClass no.. how can I set the context of the usercontrol to itself?)
UPDATE #2
I've added a GoogleDrive link to the project here if anyone wants to try the solution
Thanks
Combobox is not suitable control for multiselection, because it has given behaviour, that when yo select item, Combobox closes itself. That's why Combobox doest not have SelectionMode property like ListBox. I think that ListBox inside expander is what you need.
Generic Types are not a way to go. WPF handles this different, better way. Take listbox as an example. If you bind listbox.itemssource to generic observable collection, and you try to define e.g ItemTemplate, you get full intellisense when writing bindings and warning if you bind to not existing property. http://visualstudiomagazine.com/articles/2014/03/01/~/media/ECG/visualstudiomagazine/Images/2014/03/Figure8.ashx WPF designer automatically recognizes type parameter of your observable collection. Of cousre you need to specify type of datacontext in your page by using something like this: d:DataContext="{d:DesignInstance search:AdvancedSearchPageViewModel}". However your control dont have to be and shouldn't be aware of type of items.
Following example demonstrates control that meets your requirements:
<Expander>
<Expander.Header>
<ItemsControl ItemsSource="{Binding ElementName=PART_ListBox, Path=SelectedItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Mode=OneWay}" />
<Run Text=";" />
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Expander.Header>
<Expander.Content>
<ListBox x:Name="PART_ListBox" SelectionMode="Multiple">
<ListBox.ItemsSource>
<x:Array Type="system:String">
<system:String>ABC</system:String>
<system:String>DEF</system:String>
<system:String>GHI</system:String>
<system:String>JKL</system:String>
</x:Array>
</ListBox.ItemsSource>
</ListBox>
</Expander.Content>
</Expander>
I reccomend you to create control derived from ListBox (not usercontrol).
I have hardcoded datatemplates, but you should expose them in your custom dependency properties and use TemplateBinding in you control template. Of course you need to modify expander so it looks like combobox and ListBoxItem style so it looks like CheckBox, but it is ease.

How to create attached property for ItemsControl

I have the following ItemsControl, as shown it has hard-coded values, I would like to shift these values into an attached property, probably an ObservableCollection or something similar.
How to create this attached property and how to bind it.
<ItemsControl Grid.Column="0" VerticalAlignment="Stretch" Name="ItemsSelected">
<sys:Double>30</sys:Double>
<sys:Double>70</sys:Double>
<sys:Double>120</sys:Double>
<sys:Double>170</sys:Double>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="SlateGray" Width="18" Height="4"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Top" Value="{Binding}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
[EDIT]
So I think I have the attached property figured:
public static class ScrollBarMarkers
{
public static readonly DependencyProperty MarkersSelectedCollectionProperty =
DependencyProperty.RegisterAttached("MarkersSelectedCollection", typeof(ObservableCollection<double>), typeof(ScrollBarMarkers), new PropertyMetadata(null));
public static ObservableCollection<double> GetMarkersSelectedCollection(DependencyObject obj)
{
return (ObservableCollection<double>)obj.GetValue(MarkersSelectedCollectionProperty);
}
public static void SetMarkersSelectedCollection(ItemsControl obj, ObservableCollection<double> value)
{
obj.SetValue(MarkersSelectedCollectionProperty, value);
}
}
What I'm wondering now is the best way to get the ItemsControl object before calling the following in the selection changed behavior:
ScrollBarMarkers.SetMarkersSelectedCollection(ItemsControl, initSelected);
The style of the customized vertical scrollbar is setup in the Window.Resources
The behavior is set up on the DataGrid like so:
<DataGrid Name="GenericDataGrid">
<i:Interaction.Behaviors>
<helpers:DataGridSelectionChanged />
</i:Interaction.Behaviors>
</DataGrid>
My selection changed behavior:
public class DataGridSelectionChanged : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectionChanged += DataGrid_SelectionChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.SelectionChanged -= DataGrid_SelectionChanged;
}
void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ObservableCollection<double> initSelected = new ObservableCollection<double>();
initSelected.Add(30);
initSelected.Add(60);
initSelected.Add(100);
//Just trying to figure out how best to get the ItemsControl object.
ScrollBarMarkers.SetMarkersSelectedCollection(itemsControlObj, initSelected);
}
}
Below is an example of the markers in the scrollbar, a ItemsControl has been added to the custom vertical scrollbar as per the code right at the top of the question.
If I understand your question, you want bind an ObservableCollection to ItemsControl and when the items are long the scrollbar will appear.
This solution could serve you.
[I will working with MVVM]
You can create a ObservableCollection in your code.
private ObservableCollection<int> _list = new ObservableCollection<int>();
public ObservableCollection<int> List
{
get { return _list ; }
set { _list = value; RaisePropertyChanged("List"); }
}
Now, you binding Collection to ItemsControl
<ScrollViewer HorizontalAlignment="Left" Width="254" Height="Auto" >
<ItemsControl x:Name="ItemsControlComputers" ItemsSource="{Binding List, Mode=TwoWay}" Height="Auto"
HorizontalAlignment="Left" Width="254" ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
Background="{x:Null}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Width="254">
<TextBlock Text="{Binding }" Margin="4,0,0,5" VerticalAlignment="Center">
</TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
Went down the wrong track with this instead of creating a DependencyProperty I should have just created a plain property, however because it is UI related I did not want it with my ViewModel. So I created a class with singleton pattern in the same namespace as my behavior and other attached properties. This also means I can set the collection from any behaviors.
Here is the binding:
<ItemsControl ItemsSource="{Binding Source={x:Static helpers:MyClass.Instance}, Path=SelectedMarkers}">
Here is the class with singleton pattern
public class MyClass : INotifyPropertyChanged
{
public static ObservableCollection<double> m_selectedMarkers = new ObservableCollection<double>();
public ObservableCollection<double> SelectedMarkers
{
get
{
return m_selectedMarkers;
}
set
{
m_selectedMarkers = value;
NotifyPropertyChanged();
}
}
private static MyClass m_Instance;
public static MyClass Instance
{
get
{
if (m_Instance == null)
{
m_Instance = new MyClass();
}
return m_Instance;
}
}
private MyClass()
{
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}

Categories

Resources