I want to bind Groupboxes to my stackpanel.
c#:
internal ObservableCollection<GroupBox> BindingTest
{
get
{
ObservableCollection<GroupBox> collection = new ObservableCollection<GroupBox>();
for (int counter = 0; counter < 5; counter++)
{
GroupBox groupBox = new GroupBox();
groupBox.Header = " ... Header ... ";
groupBox.Content = " ... Content ... ";
collection.Add(groupBox);
}
return collection;
}
}
and the xaml code:
<StackPanel x:Name="Dynamic" Grid.Row="1" Margin="29,118,6,0">
<ItemsControl ItemsSource="{Binding Path=BindingTest}" Height="482">
<ItemsControl.ItemTemplate>
<DataTemplate>
<GroupBox>
<GroupBox.Header>{Binding Path=Header}</GroupBox.Header>
<GroupBox.Content>{Binding Path=Content}</GroupBox.Content>
</GroupBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
where does my error in reasoning?
In WPF Binding, you usually don't bind to Controls, you just bind to objects and let the Markup handle UI details. Ideally your objects should implement INotifyPropertyChanged to notify the Binding of property changes. Control creation is handled already by the DataTemplate, it already creates a GroupBox for you and binds its properties to the properties of the object used in the binding.
Your ViewModel should, if possible, not know anything about the UI, or use any Controls. The MVVM pattern is meant to decouple UI and Data/Logic.
The class of your data objects could look something like this:
public class DataObject : INotifyPropertyChanged
{
private string header;
private string content;
public string Header {
get { return header; }
set { header = value; RaisePropertyChanged("Header"); }
}
public string Content {
get { return content; }
set { content= value; RaisePropertyChanged("Content"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The property you bind to would also look different:
internal ObservableCollection<DataObject> BindingTest
{
get
{
ObservableCollection<DataObject> collection = new ObservableCollection<DataObject>();
for (int counter = 0; counter < 5; counter++)
{
DataObject item = new DataObject ();
item.Header = " ... Header ... ";
item.Content = " ... Content ... ";
collection.Add(item );
}
return collection;
}
}
I assume creating a new Collection every time the property is accessed is just for testing purposes.
Just change the BindingTest property into public field.. .It will automatically bind the data to the UI...
Basically xaml allows you to bind the public properties to the UI as we have limitation over the other fields like internal and private..
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public ObservableCollection<GroupBox> BindingTest
{
get
{
ObservableCollection<GroupBox> collection = new ObservableCollection<GroupBox>();
for (int counter = 0; counter < 5; counter++)
{
GroupBox groupBox = new GroupBox();
groupBox.Header = " ... Header ... ";
groupBox.Content = " ... Content ... ";
collection.Add(groupBox);
}
return collection;
}
}
}
xaml code as follows
<Grid >
<StackPanel x:Name="Dynamic" >
<ItemsControl ItemsSource="{Binding BindingTest, RelativeSource={RelativeSource AncestorType={x:Type this:MainWindow}}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<GroupBox Header="{Binding Path=Header}"
Content="{Binding Path=Content}">
</GroupBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
Related
I create multiple checkbox using ItemsControl in my WPF. But I need to make a limit by 20 for checkbox that can be checked/ticked by user. How do I can check the checked checkbox?
I tried to research this as much as I can, and even binding checkbox to multiple command, but none of it is working. Below is my code to get through the checkbox that were inside the Itemscontrol. after, IsChecked.
for (int i = 0; i < ItemsControlUnitPerStrip.Items.Count; i++)
{
ContentPresenter container = (ContentPresenter)ItemsControlUnitPerStrip.ItemContainerGenerator.ContainerFromItem(ItemsControlUnitPerStrip.Items[i]);
CheckBox checkBoxChecked = container.ContentTemplate.FindName("CheckBoxUnitPerStrip", container) as CheckBox;
if (checkBoxChecked.IsChecked == true)
{
//iOPC.WriteTag(checkBoxChecked.Uid, checkBoxChecked.IsChecked);
}
}
My XAML code
<GroupBox x:Name="GroupBoxSamplingModeStrip" Header="Unit Per Strip" Grid.Row="0" Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="ItemsControlUnitPerStrip"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding StripRowsCount}"
Columns="{Binding StripColumnsCount}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox x:Name="CheckBoxUnitPerStrip"
Uid="{Binding Tag}">
<CheckBox.ToolTip>
<ToolTip x:Name="TootlTipUnitPerStrip">
<TextBlock Text="{Binding Key}"/>
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</GroupBox>
Here the function code on how I generate the checkbox
private void initializeUnitPerStrip()
{
unitPerStrip = new List<UtilitiesModel>();
int totalRow = samplingModeModel.StripRows = 7;
int totalCol = samplingModeModel.StripColumn = 15;
int frontOffset = 8;
int behindOffset = 0;
for (int c = 1; c < totalCol; c++)
{
for (int r = 1; r < totalRow; r++)
{
unitPerStrip.Add(new UtilitiesModel
{
Key = $"[{c}, {r}]",
Tag = $"{UTAC_Tags.S7Connection}DB{406},X{frontOffset}.{behindOffset}"
});
}
}
ItemsControlUnitPerStrip.ItemsSource = unitPerStrip;
}
1) Binding checkbox property with notify property changed events:
public class UtilitiesModel : NotifyBase
{
private bool _IsChecked = false;
...
// Key
// Tag
...
public bool IsChecked
{
get {return _IsChecked;}
set
{
_IsChecked = value;
OnPropertyChanged("IsChecked");
}
}
}
For convenience, the part responsible for events is placed in a separate small class:
public class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
XAML changes:
..
<CheckBox x:Name="CheckBoxUnitPerStrip"
Uid="{Binding Tag}"
IsChecked="{Binding IsChecked}">
<CheckBox.ToolTip>
<ToolTip x:Name="TootlTipUnitPerStrip">
<TextBlock Text="{Binding Key}" />
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
..
2) Next we shall track events of changing state of checkboxes and add a counter for checked checkboxes;
A slight change in function:
private void initializeUnitPerStrip()
{
..
for (int c = 1; c < totalCol; c++)
{
for (int r = 1; r < totalRow; r++)
{
UtilitiesModel item = new UtilitiesModel
{
Key = "[{c}, {r}]",
Tag = "{UTAC_Tags.S7Connection}DB{406},X{frontOffset}.{behindOffset}"
};
item.PropertyChanged += PropertyChangedFunc;
unitPerStrip.Add(item);
}
}
ItemsControlUnitPerStrip.ItemsSource = unitPerStrip;
}
Add func for checking property changed events:
private void PropertyChangedFunc(object sender, PropertyChangedEventArgs e)
{
UtilitiesModel obj = sender as UtilitiesModel;
if(obj==null)return;
if (e.PropertyName == "IsChecked")
{
iCount1 = obj.IsChecked ? iCount1 + 1 : iCount1 - 1;
if (iCount1 > 19) //Block checking
{
obj.IsChecked = false;
}
}
}
Where iCount1 - is a counter checked checkboxes, just declare it anywhere, for example in samplingModeModel
This answer uses MVVM, so the names of your controls in XAML have been removed as they are not needed in MVVM. Your XAML would look like this:
<Button Content="Count CheckBoxes" Command="{Binding CommandCount}"
HorizontalAlignment="Left"/>
<GroupBox Header="Unit Per Strip" Grid.Row="0" Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
ItemsSource="{Binding Path=UnitPerStrip}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding StripRowsCount}"
Columns="{Binding StripColumnsCount}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Uid="{Binding Tag}"
IsChecked="{Binding IsChecked}">
<CheckBox.ToolTip>
<ToolTip >
<TextBlock Text="{Binding Key}"/>
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</GroupBox>
The only real difference in the XAML is adding the binding to the 'IsChecked' property in the CheckBox, and setting up a binding to a property called 'UnitPerStrip' for the ItemsSource of the ItemsControl.
In the ViewModel then, you need to set up the UnitPerStrip property:
private List<UtilitiesModel> unitPerStrip;
public List<UtilitiesModel> UnitPerStrip
{
get
{
return unitPerStrip;
}
set
{
if (value != unitPerStrip)
{
unitPerStrip = value;
NotifyPropertyChanged("UnitPerStrip");
}
}
}
The UtilitiesModel class needs a new property called IsChecked to keep track of when the CheckBox is checked. That way you don't have to muck about with messy UI code. It can all be done neatly in the back-end data.
public class UtilitiesModel
{
public string Key { get; set; }
public string Tag { get; set; }
public bool IsChecked { get; set; }
}
The code for generating the CheckBoxes doesn't change a lot. You just need to make sure to add in the IsChecked property and then assign the results to the UnitPerStrip property when finished.
private void initializeUnitPerStrip()
{
List<UtilitiesModel> ups = new List<UtilitiesModel>();
int totalRow = samplingModeModel.StripRows = 7;
int totalCol = samplingModeModel.StripColumn = 15;
int frontOffset = 8;
int behindOffset = 0;
for (int c = 1; c < totalCol; c++)
{
for (int r = 1; r < totalRow; r++)
{
ups.Add(new UtilitiesModel
{
Key = $"[{c}, {r}]",
Tag = $"{UTAC_Tags.S7Connection}DB{406},X{frontOffset}.{behindOffset}",
IsChecked = false;
});
}
}
UnitPerStrip = ups;
}
Then the code to check how many CheckBoxes are checked is very straightforward. It only examines the data in the ViewModel and never has to worry about messing with any of the messiness of the UI:
private void Count()
{
int count = 0;
foreach (UtilitiesModel item in UnitPerStrip)
{
if (item.IsChecked) count++;
}
MessageBox.Show(count.ToString());
}
In case you don't like to add a special IsChecked property to your model you could use a IValueConverter.
A property like IsChecked is view related and shouldn't be part of the view model (if you can avoid it). When you change the control that binds to the view model you might also need to change this property or rename it (e.g. IsExpanded etc.).
I recommend to avoid properties that reflect a visual state inside a view model in general. Your view model would become bloated if adding properties like IsVisible, IsPressed, IsToggled. This properties rather belong to a Control.
The converter (or DataTrigger) approach leaves your binding data models unchanged (with data related properties only). To keep the view model clean and free from UI logic like adjusting properties like IsVisible or IsChecked to reflect e.g. reordering of the collection to the view (e.g. insert or sort operations), all UI logic and visual details like enabling or disabling the controls
should be handled by converters and triggers only:
<!-- Decalare the converter and set the MaxCount property -->
<Window.Resources>
<local:ItemCountToBoolenaConverter x:Key="ItemCountToBoolenaConverter"
MaxCount="20" />
</Window.Resources>
<! -- The actual DataTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox x:Name="CheckBoxUnitPerStrip"
IsEnabled="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}, Converter={StaticResource ItemCountToBoolenaConverter}}">
<CheckBox.ToolTip>
<ToolTip x:Name="TootlTipUnitPerStrip">
<TextBlock Text="{Binding Key}" />
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
The ItemCountToBoolenaConverter:
[ValueConversion(typeof(ListBoxItem), typeof(bool))]
class ItemCountToBoolenaConverter : IValueConverter
{
public int MaxCount { get; set; }
#region Implementation of IValueConverter
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ListBoxItem itemContainer && TryFindParentElement(itemContainer, out ItemsControl parentItemsControl))
{
return parentItemsControl.Items.IndexOf(itemContainer.Content) < this.MaxCount;
}
return Binding.DoNothing;
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
// Consider to make this an Extension Method for DependencyObject
private bool TryFindVisualParent<TParent>(DependencyObject child, out TParent resultElement) where TParent : DependencyObject
{
resultElement = null;
if (child == null)
return false;
DependencyObject parentElement = VisualTreeHelper.GetParent(child);
if (parentElement is TParent)
{
resultElement = parentElement as TParent;
return true;
}
return TryFindVisualParent(parentElement, out resultElement);
}
}
I have different groups of controls bound to different categories of ViewModel classes.
The ViewModels are
MainViewModel
VideoViewModel
AudioViewModel
Question
How can I set the DataContext with XAML instead of C#?
1. I tried adding DataContext="{Binding VideoViewModel}" to the ComboBox XAML, but it didn't work and the items came up empty.
2. I also tried grouping all the ComboBoxes of a certain category inside a UserControl with the DataContext:
<UserControl DataContext="{Binding VideoViewModel}">
<!-- ComboBoxes in here -->
</UserControl>
3. Also tried setting the <Window> DataContext to itself DataContext="{Binding RelativeSource={RelativeSource Self}}"
Data Context
I'm currently setting the DataContext this way for the different categories of controls:
public MainWindow()
{
InitializeComponent();
// Main
this.DataContext =
tbxInput.DataContext =
tbxOutput.DataContext =
cboPreset.DataContext =
MainViewModel.vm;
// Video
cboVideo_Codec.DataContext =
cboVideo_Quality.DataContext =
tbxVideo_BitRate.DataContext =
cboVideo_Scale.DataContext =
VideoViewModel.vm;
// Audio
cboAudio_Codec.DataContext =
cboAudio_Quality.DataContext =
tbxAudio_BitRate.DataContext =
tbxAudio_Volume.DataContext =
AudioViewModel.vm;
}
XAML ComboBox
<ComboBox x:Name="cboVideo_Quality"
DataContext="{Binding VideoViewModel}"
ItemsSource="{Binding Video_Quality_Items}"
SelectedItem="{Binding Video_Quality_SelectedItem}"
IsEnabled="{Binding Video_Quality_IsEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="105"
Height="22"
Margin="0,0,0,0"/>
Video ViewModel Class
public class VideoViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void OnPropertyChanged(string prop)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(prop));
}
}
public VideoViewModel() { }
public static VideoViewModel _vm = new VideoViewModel();
public static VideoViewModel vm
{
get { return _vm; }
set
{
_vm = value;
}
}
// Items Source
private List<string> _Video_Quality_Items = new List<string>()
{
"High",
"Medium",
"Low",
};
public List<string> Video_Quality_Items
{
get { return _Video_Quality_Items; }
set
{
_Video_Quality_Items = value;
OnPropertyChanged("Video_Quality_Items");
}
}
// Selected Item
private string _Video_Quality_SelectedItem { get; set; }
public string Video_Quality_SelectedItem
{
get { return _Video_Quality_SelectedItem; }
set
{
if (_Video_Quality_SelectedItem == value)
{
return;
}
_Video_Quality_SelectedItem = value;
OnPropertyChanged("Video_Quality_SelectedItem");
}
}
// Enabled
private bool _Video_Quality_IsEnabled;
public bool Video_Quality_IsEnabled
{
get { return _Video_Quality_IsEnabled; }
set
{
if (_Video_Quality_IsEnabled == value)
{
return;
}
_Video_Quality_IsEnabled = value;
OnPropertyChanged("Video_Quality_IsEnabled");
}
}
}
You can instantiate an object in xaml:
<Window.DataContext>
<local:MainWindowViewmodel/>
</Window.DataContext>
And you could do that for your usercontrol viewmodels as well.
It's more usual for any child viewmodels to be instantiated in the window viewmodel. Exposed as public properties and the datacontext of a child viewmodel then bound to that property.
I suggest you google viewmodel first and take a look at some samples.
I'm not sure if this is the correct way, but I was able to bind groups of ComboBoxes to different ViewModels.
I created one ViewModel to reference them all.
public class VM: INotifyPropertyChanged
{
...
public static MainViewModel MainView { get; set; } = new MainViewModel ();
public static VideoViewModel VideoView { get; set; } = new VideoViewModel ();
public static AudioViewModel AudioView { get; set; } = new AudioViewModel ();
}
I used Andy's suggestion <local:VM> in MainWindow.xaml.
<Window x:Class="MyProgram.MainWindow"
...
xmlns:local="clr-namespace:MyProgram"
>
<Window.DataContext>
<local:VM/>
</Window.DataContext>
And used a UserControl with DataContext set to VideoView, with ComboBoxes inside.
Instead of a UserControl, can also just use VideoView.Your_Property_Name on each binding.
<UserControl DataContext="{Binding VideoView}">
<StackPanel>
<ComboBox x:Name="cboVideo_Quality"
ItemsSource="{Binding Video_Quality_Items}"
SelectedItem="{Binding Video_Quality_SelectedItem}"
IsEnabled="{Binding Video_Quality_IsEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="105"
Height="22"
Margin="0,0,0,0"/>
<!-- Other ComboBoxes with DataContext VideoView in here -->
</StackPanel>
</UserControl>
Then to access one of the properties:
VM.VideoView.Video_Codec_SelectedItem = "x264";
VM.VideoView.Video_Quality_SelectedItem = "High";
VM.AudioView.Audio_Codec_SelectedItem = "AAC";
VM.AudioView.Audio_Quality_SelectedItem = "320k";
Others have obviously provided good answers, however, the underlying miss of your binding is your first set of DataContext = = = = = to the main view model.
Once you the main form's data context to the MAIN view model, every control there-under is expecting ITS STARTING point as the MAIN view model. Since the MAIN view model does not have a public property UNDER IT of the video and audio view models, it cant find them to bind do.
If you remove the "this.DataContext =", then there would be no default data context and each control SHOULD be able to be bound as you intended them.
So change
this.DataContext =
tbxInput.DataContext =
tbxOutput.DataContext =
cboPreset.DataContext =
MainViewModel.vm;
to
tbxInput.DataContext =
tbxOutput.DataContext =
cboPreset.DataContext =
MainViewModel.vm;
I am developing a WPF application in which i have a ComboBox,like this
<ComboBox SelectedIndex="1" Grid.Column="2" Grid.Row="1" ItemsSource="{Binding VipCodes}"
SelectedItem="{Binding SelectedVipCode,Mode=OneWay}" Style="{StaticResource DefaultComboBoxStyle}" x:Name="vipCode" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
after loading the page when the selection changed, i need to update a value of a property.
I did like hooking up the selection changed event and set the value.
But when the page loaded, the event fires and the value of a property is set.
how can i bypass this?
Just set a global variable if you absolutely have to. var SkipOnce = true; and then on page load set it to false at the end. And then in your selection changed event add: if (SkipOnce==false) { //do stuff }
Described behavior can be achieved without event handlers in code behind, by ViewModel only. If DataContext of ComboBox is set to view model, then following code will do the job:
public MainWindowViewModel()
{
for (int i = 0; i < 10; i++)
{
_vipCodes.Add(new VipCode() { Description = i.ToString() });
}
SelectedVipCode = _vipCodes[3];
}
private ObservableCollection<VipCode> _vipCodes = new ObservableCollection<VipCode>();
public ObservableCollection<VipCode> VipCodes
{
get { return _vipCodes; }
}
private VipCode _selectedVipCode;
public VipCode SelectedVipCode
{
get { return _selectedVipCode; }
set
{
_selectedVipCode = value;
OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName] string property = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
I have two listbox define below:
<ListBox x:Name="RemoteListBox" HorizontalAlignment="Right" Margin="0,88.5,8,0"
Width="382.5"
HorizontalContentAlignment="Stretch"
ItemsSource ="{Binding RemoteItemsList}"
SelectedIndex="0">
</ListBox>
<ListBox x:Name="LibraryListBox"
Margin="4.5,88.5,437,0"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding LibraryItemsList}"
SelectedIndex="0">
</ListBox>
My viewmodel
private ObservableCollection<MotionTitleItem> _remoteItemsList;
public ObservableCollection<MotionTitleItem> RemoteItemsList
{
get { return _remoteItemsList; }
set
{
_remoteItemsList = value;
NotifyPropertyChanged("RemoteItemsList");
}
}
private ObservableCollection<MotionTitleItem> _libraryItemsList
public ObservableCollection<MotionTitleItem> LibraryItemsList
{
get { return _libraryItemsList; }
set
{
_libraryItemsList = value;
NotifyPropertyChanged("LibraryItemsList");
}
}
I bind two ListBox ItemSource with a ObserverableCollection define below:
var listMotion = new ObservableCollection<MotionTitleItem>();
foreach (MotionInfo info in listMotionInfo)
{
var motionTitleItem = new MotionTitleItem();
listMotion.Add(motionTitleItem);
}
viewModel.RemoteItemsList = listMotion;
viewModel.LibraryItemsList = listMotion;
MotionTitleItem is a custom user control.
My problem is only the first ListBox with ItemSource binding with RemoteListItem displays the Item in UI, the other doest not.
If I bind two ListBox ItemSource with 2 ObserverableCollection, the problem solved:
var listMotion = new ObservableCollection<MotionTitleItem>();
var listMotion2 = new ObservableCollection<MotionTitleItem>();
foreach (MotionInfo info in listMotionInfo)
{
var motionTitleItem = new MotionTitleItem();
listMotion.Add(motionTitleItem);
var motionTitleItem2 = new MotionTitleItem();
listMotion2.Add(motionTitleItem2);
}
viewModel.RemoteItemsList = listMotion;
viewModel.LibraryItemsList = listMotion2;
Could someone explain to me where is the point of the first scenario problem?
I don't know why you used two temporary list for this. You can directly add items to your Observable collection. Try this :
foreach (MotionInfo info in listMotionInfo)
{
viewModel.RemoteItemsList.Add(info);
viewModel.LibraryItemsList.Add(info);
}
Below, I tried to create the solution for you.
I assumed the model MotionTitleItem.
public class MotionTitleItem
{
string _name = string.Empty;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
try
{
PropertyChangedEventHandler eventHandler = this.PropertyChanged;
if (null == eventHandler)
return;
else
{
var e = new PropertyChangedEventArgs(propertyName);
eventHandler(this, e);
}
}
catch (Exception)
{
throw;
}
}
}
My view model for this application is:
public class MotionTitleItemViewModel : INotifyPropertyChanged
{
ObservableCollection<MotionTitleItem> _remoteItemsList = new ObservableCollection<MotionTitleItem>();
public ObservableCollection<MotionTitleItem> RemoteItemsList
{
get { return _remoteItemsList; }
set { _remoteItemsList = value; }
}
ObservableCollection<MotionTitleItem> _libraryItemsList = new ObservableCollection<MotionTitleItem>();
public ObservableCollection<MotionTitleItem> LibraryItemsList
{
get { return _libraryItemsList; }
set { _libraryItemsList = value; }
}
public MotionTitleItemViewModel()
{
MotionTitleItem motion;
for (int i = 0; i < 10; i++)
{
motion = new MotionTitleItem();
motion.Name = "Name " + i.ToString();
this.LibraryItemsList.Add(motion);
this.RemoteItemsList.Add(motion);
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
try
{
PropertyChangedEventHandler eventHandler = this.PropertyChanged;
if (null == eventHandler)
return;
else
{
var e = new PropertyChangedEventArgs(propertyName);
eventHandler(this, e);
}
}
catch (Exception)
{
throw;
}
} }
My View is :
<Window x:Class="WPFExperiments.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox x:Name="RemoteListBox" HorizontalAlignment="Right" Margin="0,0.5,8,0"
Width="382.5"
HorizontalContentAlignment="Stretch"
ItemsSource ="{Binding RemoteItemsList}"
SelectedIndex="0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox x:Name="LibraryListBox"
Margin="4.5,0.5,437,0"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding LibraryItemsList}"
SelectedIndex="0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
In code behind of this window i set DataContext to view model.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MotionTitleItemViewModel();
}}
This code is working for me.
Here is screenshot of the output.
Vote this answer if you find it useful.
Have fun!
I have a DataTemplate defined as follows
<DataTemplate x:Key="PasswordViewerTemplate">
<StackPanel>
<TextBlock Text="{Binding PasswordChar, ElementName=this}"
Visibility="Visible" />
<TextBox Text="{Binding PasswordText}"
Visibility="Collapsed" />
</StackPanel>
</DataTemplate>
I want to be able to toggle visibilities of the TextBlock and the TextBox each time the user clicks on the StackPanel. I tried setting a MouseLeftButtonUp event handler on the StackPanel but this throws an exception
Object reference not set to an instance of an object
Is there another way to achieve this? Maybe in XAML itself using triggers?
Also, this might be relevant. The above template is one of two that is applied to a ListBox by a template selector. The ListBox itself is within a Grid and both templates are defined within the Grid.Resources section.
EDIT 1
I tried setting the event as follows
<StackPanel MouseLeftButtonUp="OnPasswordViewerMouseLeftButtonUp">
...
</StackPanel>
private void OnPasswordViewerMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var sp = sender as StackPanel;
if( ( sp == null ) || ( sp.Children.Count != 2 ) ) {
return;
}
var passwordText = sp.Children[0] as TextBlock;
var plainText = sp.Children[1] as TextBox;
if( ( passwordText == null ) || ( plainText == null ) ) {
return;
}
passwordText.Visibility = ( passwordText.Visibility == Visibility.Visible ) ?
Visibility.Collapsed : Visibility.Visible;
plainText.Visibility = ( plainText.Visibility == Visibility.Visible ) ?
Visibility.Collapsed : Visibility.Visible;
}
One of the solutions is to bind visibility of the TextBox and TextBlock to properties of the class which is used as DataContext for the StackPanel. Here is a sample implementation:
Xaml code:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="PasswordViewerTemplate">
<StackPanel PreviewMouseUp="StackPanel_PreviewMouseUp">
<TextBlock Text="{Binding Path=PasswordChar}"
Visibility="{Binding Path=TextBlockVisibility}" />
<TextBox Text="{Binding Path=PasswordText}"
Visibility="{Binding Path=TextBoxVisibility}" />
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ListBox x:Name="lbox" ItemTemplate="{StaticResource ResourceKey=PasswordViewerTemplate}" ItemsSource="{Binding}"/>
</Grid>
And C# code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<Some> items = new ObservableCollection<Some>();
for (int i = 0; i < 10; i++)
{
items.Add(new Some(string.Format("passwordChar {0}", i + 1), string.Format("passwordText {0}", i + 1), Visibility.Visible, Visibility.Collapsed));
}
this.lbox.ItemsSource = items;
}
private void StackPanel_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
Some some = (sender as StackPanel).DataContext as Some;
some.TextBlockVisibility = ToggleVisibility(some.TextBlockVisibility);
some.TextBoxVisibility = ToggleVisibility(some.TextBoxVisibility);
}
private Visibility ToggleVisibility(Visibility visibility)
{
return visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
}
}
public class Some:INotifyPropertyChanged
{
private string _passwordChar;
private string _passwordText;
private Visibility _textBlockVisibility, _textBoxVisibility;
public string PasswordChar { get { return this._passwordChar; } set { this._passwordChar = value; } }
public string PasswordText { get { return this._passwordText; } set { this._passwordText = value; } }
public Visibility TextBlockVisibility
{
get { return this._textBlockVisibility; }
set
{
this._textBlockVisibility = value;
RaisePropertyChanged("TextBlockVisibility");
}
}
public Visibility TextBoxVisibility
{
get { return this._textBoxVisibility; }
set
{
this._textBoxVisibility = value;
RaisePropertyChanged("TextBoxVisibility");
}
}
public Some(string passwordChar, string passwordText, Visibility textBlockVisibility, Visibility textBoxVisibility)
{
this._passwordChar = passwordChar;
this._passwordText = passwordText;
this._textBlockVisibility = textBlockVisibility;
this._textBoxVisibility = textBoxVisibility;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
Why dont you bind the item's visibility in your view model?
an Example.
<Textblock Test="{Binding passwordText,ElementName=This}" Visibility="{Binding passwordTextVisibility}"/>
in your ViewModel say
public Visibility passwordTextVisibility
{
getters and setters here
}
and on your mouse event, you would need some sort of routed event inside the stack panel. an example:
inside the stack panel you would need mouse. whatever you need. read a little about routed events
Example. if PreviewMouseLeftButtonUp does not work.
<StackPanel Mouse.MouseUp="MouseButtonUpEventHandler"/>
in the view model
public void MouseButtonUpEventHandler (RoutedEvent e)
{
//logic here to check if it's left mouse if it is then set visibility
}
}