I am still new in MVVM and WPF
and I've looked at some examples however still did not found exactly the answers to my question.
I have a ListBox which each Item should add a new user control using ObservableCollection. In the user control I have an several Text blocks which I want to bound the texts of them to the same ObservableCollection which contain the Data.
However I am not sure how to bind the Text blocks to the ObservableCollection
Would be happy for a code example.
I'm also attaching my code, this is my userControl XAML:
<UserControl x:Class="ProtocolAnalyzerGui.UserControlls.MenuControlls.UCSingleLine"
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="100" d:DesignWidth="300">
<Grid Background="#FF454545">
<Grid.RowDefinitions>
<RowDefinition Height="1*"></RowDefinition>
<RowDefinition Height="1*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="TBHeader" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center" ></TextBlock>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="10"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="10"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="10"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock x:Name="TBDatanTime" Grid.Column="0" Foreground="White" Text="{Binding DataAndTime }" ></TextBlock>
<TextBlock x:Name="TBComPort" Grid.Column="2" Foreground="White" Text="{Binding ComPort }" ></TextBlock>
<TextBlock x:Name="TBTranslation" Grid.Column="4" Foreground="White" Text="{Binding Translation }" ></TextBlock>
<TextBlock x:Name="TBDataBytesArray" Grid.Column="6" Foreground="White" Text="{Binding Header }" ></TextBlock>
</Grid>
</Grid>
</UserControl>
in the main window XAML:
<ScrollViewer Grid.Row="3" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ListBox x:Name="LBListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<myControlls:UCSingleLine x:Name="DataUserContoll"
DataContext="{Binding DataForGui}"></myControlls:UCSingleLine>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
the binding of the listbox Itemsource is inside this function:
private void MI_SerialPortStart_Click(object sender, RoutedEventArgs e)
{
LBListBox.ItemsSource = DataForGui;
_SerialPortTakeCare.Start();
}
also attaching my dataCode:
public class Data : INotifyPropertyChanged
{
private string _DataAndTime;
public string DataAndTime
{
get { return _DataAndTime; }
set
{
_DataAndTime = value;
OnPropertyChanged("DataAndTime");
}
}
private string _ComPort;
public string ComPort
{
get { return _ComPort; }
set
{
_ComPort = value;
OnPropertyChanged("ComPort");
}
}
private string _Translation;
public string Translation
{
get { return _Translation; }
set
{
_Translation = value;
OnPropertyChanged("Translation");
}
}
private string _Header;
public string Header
{
get { return _Header; }
set
{
_Header = value;
OnPropertyChanged("Header");
}
}
private string _Data_ARR;
public string Data_ARR
{
get { return _Data_ARR; }
set
{
_Data_ARR = value;
OnPropertyChanged("Data_ARR");
}
}
public Data()
{
_ComPort = "";
_Data_ARR = "";
_DataAndTime = "";
_Header = "";
_Translation = "";
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You just need to set the data context of your user control and then add the appropriate bindings.
In your main window, this line should look as follows:
<local:UserControl1 DataContext="{Binding}" x:Name="DataUserContoll"/>
And in the user control, the code should look more like this:
<TextBlock x:Name="TBDatanTime" Grid.Column="0" Foreground="White" Text="{Binding TextField1}" />
<TextBlock x:Name="TBComPort" Grid.Column="2" Foreground="White" Text="{Binding TextField2}"/>
<TextBlock x:Name="TBTranslation" Grid.Column="4" Foreground="White" Text="{Binding TextField3}"/>
<TextBlock x:Name="TBDataBytesArray" Grid.Column="6" Foreground="White" Text="{Binding TextField4}"/>
... and you should change the placeholder field names I have put in (e.g. "TextField1") to be the actual string properties of the objects inside your collection "DataForGui".
Related
When i try to set VirtualizationMode on my ListView to Recycling I get the error from the title:
Cannot change the VirtualizationMode attached property on an
ItemsControl after Measure is called on the ItemsHost panel.
I am trying to set the attached property programmatically but when I try to define the VirtualizationMode in XAML designer throws the same error from the title. Anyone had similar problems like this?
My view in XAML is:
<Window x:Class="FinalVirtualizationApp.MainWindow"
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:FinalVirtualizationApp"
mc:Ignorable="d"
Title="MainWindow" Height="900" Width="800"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
TextElement.Foreground="{DynamicResource MaterialDesignBody}"
TextElement.FontWeight="Regular"
TextElement.FontSize="13"
TextOptions.TextFormattingMode="Ideal"
TextOptions.TextRenderingMode="Auto"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="{DynamicResource MaterialDesignFont}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<GroupBox Grid.Row="0" Header="UI virtualization options">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<CheckBox Content="UI virtualization" VerticalAlignment="Center" IsChecked="{Binding IsUIVirtualization}"/>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="Container Recycling:" VerticalAlignment="Center"/>
<ComboBox Grid.Column="1" VerticalAlignment="Center" SelectedValue="{Binding ContainerRecyclingType}" SelectedValuePath="Content">
<ComboBoxItem>Recycling</ComboBoxItem>
<ComboBoxItem>Standard</ComboBoxItem>
</ComboBox>
</Grid>
<CheckBox Content="Deferred scrolling" Grid.Row="1" VerticalAlignment="Center" IsChecked="{Binding IsDeferredScrolling}">
</CheckBox>
<Grid Grid.Row="1" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="74*"/>
<ColumnDefinition Width="253*"/>
</Grid.ColumnDefinitions>
<Label Content="Scroll unit" VerticalAlignment="Center" Margin="0,0,0,1"/>
<ComboBox Grid.Column="1" VerticalAlignment="Center" SelectedValue="{Binding ScrollUnitType}" SelectedValuePath="Content" Grid.ColumnSpan="2" Margin="0,2,0,3">
<ComboBoxItem>Item</ComboBoxItem>
<ComboBoxItem>Pixel</ComboBoxItem>
</ComboBox>
</Grid>
</Grid>
</GroupBox>
<GroupBox Grid.Row="1" Header="Data virtualization">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<GroupBox Header="ItemsProvider">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<Label Content="Number of Items: "/>
<TextBox Width="60" Text="{Binding NumberOfItems}"/>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Label Content="Fetch delay(ms): "/>
<TextBox Width="60" Text="{Binding FetchDelay}"/>
</StackPanel>
</Grid>
</GroupBox>
<GroupBox Grid.Row="1" Header="Collection">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,2,0,0">
<TextBlock Text="Type:" Margin="5" TextAlignment="Right" VerticalAlignment="Center"/>
<RadioButton x:Name="rbNormal" GroupName="rbGroup" Margin="5" Content="List(T)" VerticalAlignment="Center" Command="{Binding CollectionTypeChangeCommand}" CommandParameter="List"/>
<RadioButton x:Name="rbVirtualizing" GroupName="rbGroup" Margin="5" Content="VirtualizingList(T)" VerticalAlignment="Center" Command="{Binding CollectionTypeChangeCommand}" CommandParameter="VirtualizingList"/>
<RadioButton x:Name="rbAsync" GroupName="rbGroup" Margin="5" Content="AsyncVirtualizingList(T)" VerticalAlignment="Center" Command="{Binding CollectionTypeChangeCommand}" CommandParameter="AsyncVirtualizingList"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,2,0,0">
<TextBlock Text="Page size:" Margin="5" TextAlignment="Right" VerticalAlignment="Center"/>
<TextBox x:Name="tbPageSize" Margin="5" Text="{Binding PageSize}" Width="60" VerticalAlignment="Center"/>
<TextBlock Text="Page timeout (s):" Margin="5" TextAlignment="Right" VerticalAlignment="Center"/>
<TextBox x:Name="tbPageTimeout" Margin="5" Text="{Binding PageTimeout}" Width="60" VerticalAlignment="Center"/>
</StackPanel>
</StackPanel>
</GroupBox>
</Grid>
</GroupBox>
<StackPanel Orientation="Horizontal" Grid.Row="2">
<TextBlock Text="Memory Usage:" Margin="5" VerticalAlignment="Center"/>
<TextBlock x:Name="tbMemory" Margin="5" Width="80" Text="{Binding MemoryUsage}" VerticalAlignment="Center"/>
<Button Content="Refresh" Margin="5" Width="100" VerticalAlignment="Center" Command="{Binding RefreshCommand}"/>
</StackPanel>
<ListView Grid.Row="3" Name="lvItems">
<ListView.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding DeviceName}">
<StackPanel>
</StackPanel>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
And i try to set the VirtualizationMode here:
public void SetUIVirtualizationOptions()
{
listView.SetValue(VirtualizingStackPanel.IsVirtualizingProperty, IsUIVirtualization);
listView.SetValue(ScrollViewer.IsDeferredScrollingEnabledProperty, IsDeferredScrolling);
if(ContainerRecyclingType == "Recycling")
listView.SetValue(VirtualizingStackPanel.VirtualizationModeProperty, VirtualizationMode.Recycling);
else
listView.SetValue(VirtualizingStackPanel.VirtualizationModeProperty, VirtualizationMode.Standard);
if (ScrollUnitType == "Item")
listView.SetValue(VirtualizingPanel.ScrollUnitProperty, ScrollUnit.Item);
else
listView.SetValue(VirtualizingPanel.ScrollUnitProperty, ScrollUnit.Pixel);
}
Edit: My Window code behind where i pass the listview to viewmodel is:
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel(lvItems);
}
}
And the viewmodel part of the code (minus getters and setters for less space taken):
public class MainWindowViewModel : BindableBase
{
#region private fields
private ListView listView;
private bool isUIVirtualization;
private bool isDeferredScrolling;
private string containerRecyclingType;
private string scrollUnitType;
private int numberOfItems;
private int fetchDelay;
private string collectionType;
private int pageSize;
private int pageTimeout;
private string memoryUsage;
private DemoItemProvider itemsProvider;
#endregion
#region commands
public RelayCommand<string> CollectionTypeChangeCommand { get; set; }
public RelayCommand RefreshCommand { get; set; }
public RelayCommand<string> ContainerRecyclingTypeChangeCommand { get; set; }
public RelayCommand<string> ScrollUnitTypeChangeCommand { get; set; }
#endregion
public MainWindowViewModel(ListView lvItems)
{
this.listView = lvItems;
PageSize = 100;
PageTimeout = 30;
NumberOfItems = 1000000;
FetchDelay = 1000;
CollectionTypeChangeCommand = new RelayCommand<string>(CollectionTypeChangeFunc);
RefreshCommand = new RelayCommand(RefreshFunc);
ContainerRecyclingTypeChangeCommand = new RelayCommand<string>(ContainerRecyclingTypeChangeFunc);
ScrollUnitTypeChangeCommand = new RelayCommand<string>(ScrollUnitTypeChangeFunc);
// use a timer to periodically update the memory usage
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 1);
timer.Tick += timer_Tick;
timer.Start();
}
private void timer_Tick(object sender, EventArgs e)
{
MemoryUsage = string.Format("{0:0.00} MB", GC.GetTotalMemory(true) / 1024.0 / 1024.0);
}
#region command methods
public void CollectionTypeChangeFunc(string type)
{
CollectionType = type;
}
public void RefreshFunc()
{
SetUIVirtualizationOptions();
itemsProvider = new DemoItemProvider(NumberOfItems, FetchDelay);
if (collectionType == "List")
{
listView.ItemsSource = new List<DataItem>(itemsProvider.FetchRange(0, itemsProvider.FetchCount()));
}
else if (collectionType == "VirtualizingList")
{
listView.ItemsSource = new VirtualizingCollection<DataItem>(itemsProvider, pageSize);
}
else if (collectionType == "AsyncVirtualizingList")
{
listView.ItemsSource = new AsyncVirtualizingCollection<DataItem>(itemsProvider, pageSize, pageTimeout * 1000);
}
}
public void ContainerRecyclingTypeChangeFunc(string type)
{
ContainerRecyclingType = type;
}
public void ScrollUnitTypeChangeFunc(string type)
{
ScrollUnitType = type;
}
public void SetUIVirtualizationOptions()
{
listView.SetValue(VirtualizingStackPanel.IsVirtualizingProperty, IsUIVirtualization);
listView.SetValue(ScrollViewer.IsDeferredScrollingEnabledProperty, IsDeferredScrolling);
if(ContainerRecyclingType == "Recycling")
listView.SetValue(VirtualizingStackPanel.VirtualizationModeProperty, VirtualizationMode.Recycling);
else
listView.SetValue(VirtualizingStackPanel.VirtualizationModeProperty, VirtualizationMode.Standard);
if (ScrollUnitType == "Item")
listView.SetValue(VirtualizingPanel.ScrollUnitProperty, ScrollUnit.Item);
else
listView.SetValue(VirtualizingPanel.ScrollUnitProperty, ScrollUnit.Pixel);
}
#endregion
}
The code source for VirtualizingStackPanel indicates that this property can only be set before initialization of the panel:
/// <summary>
/// Attached property for use on the ItemsControl that is the host for the items being
/// presented by this panel. Use this property to modify the virtualization mode.
///
/// Note that this property can only be set before the panel has been initialized
/// </summary>
public static readonly DependencyProperty VirtualizationModeProperty =
DependencyProperty.RegisterAttached("VirtualizationMode", typeof(VirtualizationMode), typeof(VirtualizingStackPanel),
new FrameworkPropertyMetadata(VirtualizationMode.Standard));
You can indeed check this behavior later in the file:
//
// Set up info on first measure
//
if (HasMeasured)
{
VirtualizationMode oldVirtualizationMode = InRecyclingMode ? VirtualizationMode.Recycling : VirtualizationMode.Standard;
if (oldVirtualizationMode != virtualizationMode)
{
throw new InvalidOperationException(SR.Get(SRID.CantSwitchVirtualizationModePostMeasure));
}
}
else
{
HasMeasured = true;
}
and there is no way (according to source code) to set this HasMeasured property back to False unless you destroy and recreate the ListView.
It is so as the message says:
Your not allowed to _change the VirtualizationMode attached property
on an ItemsControl after Measure is called on the ItemsHost panel.
This means, that if ListView is already shown virtualizing mechanism being used and you are not allowed to change it.
If you set Visibility of ListView in XAML to Collapsed and set it only later to the Visible, then you can set the VirtualizationMode in code behind to wished value, but only once(so you can't change it after ListView become visible)!
private void Button_Click(object sender, RoutedEventArgs e)
{
listView.SetValue(VirtualizingStackPanel.VirtualizationModeProperty, VirtualizationMode.Recycling);
listView.Visibility = Visibility.Visible;
}
XAML:
<ListView x:Name="listView" ... Visibility="Collapsed">
I have a ListView with textBox in one of the columns. User input on the textbox supposed to update the underlying data, that's what my requirement is, but it's not happening. I tried a work around as shown all the way below, it seems to work but I am guessing that the binding itself should take care of it without the work around.
XAML:
<ListView x:Name="listView" Grid.Row="1" ItemsSource="{Binding}" SelectionMode="Multiple">
<ListView.ItemTemplate>
<DataTemplate>
<Border Background="{Binding BackGround}">
<Grid x:Name="row">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock x:Name="tblDescription"
Grid.Row="0"
Grid.Column="0"
Margin="1"
TextWrapping="Wrap"
Text="{Binding Description}"
FontSize="{StaticResource TextStyleMediumFontSize}"
Foreground="Black"/>
<TextBlock x:Name="tblItemNumber"
Grid.Row="0"
Grid.Column="1"
Margin="1"
Text="{Binding ItemNumber}"
FontSize="{StaticResource TextStyleMediumFontSize}"
FontStyle="Italic"
Foreground="Gray"/>
<TextBox x:Name="tbQuantity"
Grid.Row="1"
Grid.Column="1"
Margin="1"
Text="{Binding Quantity}"
IsEnabled="{Binding IsEnabled}"
FontSize="{StaticResource TextStyleLargeFontSize}"
BorderBrush="DarkGray"
Foreground="Black"
InputScope="Number"
Tag="{Binding RowNumber}"
TextChanged="tbQuantity_TextChanged"
>
<TextBox.Header>Quantity</TextBox.Header>
</TextBox>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
DataRow class:
public class DataRowBase : INotifyPropertyChanged
{
#region Events
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Properties
public int RowNumber { get; set; }
public string ItemNumber { get; set; }
public string Description { get; set; }
private string _Quantity;
public string Quantity
{
get { return _Quantity; }
set {
_Quantity = value;
RaisePropertyChanged("Quantity");
}
}
#endregion
}
Work Around:
private void tbQuantity_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox tb = (TextBox)sender;
int rownumber = tb.Tag.ToString().ToInt();
UpdateQuantity(rownumber, value: tb.Text);
}
private void UpdateQuantity(int rownumber, string value)
{
try
{
DataRow datarow = OriginalSource.Where(o => o.RowNumber == rownumber).FirstOrDefault();
if (datarow != null)
{
datarow.Quantity = value;
}
}
catch (Exception ex)
{
}
}
The default Binding is OneWay, for a TwoWay you will have to declare it, for example:
Text="{Binding ItemNumber, Mode=TwoWay}"
I don't know why but my Visibility Binding isn't working ONLY in the DataTemplate. Did I forget something?
Edit: All Bindings (except for this one work perfectly)
Thats the structure.
<Window>
<Grid>
<Grid>
<Grid Grid.Row="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ItemsControl x:Name="Targets" Margin="0,4,0,4">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,5,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox TextWrapping="Wrap" Foreground="{Binding Foreground, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Address, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" Tag="{Binding}" PreviewKeyDown="ChangeLocationAddress" PreviewGotKeyboardFocus="TxtGotKeyboardFocusHandler" LostKeyboardFocus="ChangeLocationAddress" />
<Button Margin="2,0,0,0" Grid.Column="1" Content=" ↑ " Click="MoveLocationUp" Visibility="Visible" />
<Button Margin="2,0,0,0" Grid.Column="2" Content=" ↓ " Click="MoveLocationDown" Visibility="{Binding Path = UpDownButtonVisibility, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button x:Name="btnNewAddress" Grid.Row="1" Content="Neues Ziel hinzufügen" Margin="0,4,0,4" Visibility="{Binding Path=TargetButtonVisibility}"/>
</Grid>
</Grid></Grid></Grid></Window>
Codebehind:
public MapView(){
this.DataContext = this.ViewModel = new MapVM();
this.InitializeComponent();
this.Targest.Itemssource = this.ViewModel.ToLocations;
}
ViewModel:
public MapVM()
{ this.UpDownButtonVisibility = Visibility.Collapsed;
this.TargetButtonVisibility = Visibility.Collapsed;
}
private Visibility _UpDownButtonVisibility;
/// <summary>
/// Property Visibility für "↓" und "↑"
/// </summary>
public Visibility UpDownButtonVisibility
{
get { return _UpDownButtonVisibility; }
set
{
this._UpDownButtonVisibility = value;
NotifyPropertyChanged("UpDownButtonVisibility");
}
}
public Visibility TargetButtonVisibility { get; set; }
EDIT:
Program Output:
BindingExpression path error: 'UpDownButtonVisibility' property not found on 'object' ''Location' (HashCode=-794088449)'. BindingExpression:Path=UpDownButtonVisibility; DataItem='Location' (HashCode=-794088449); target element is 'Button' (Name=''); target property is 'Visibility' (type 'Visibility') 1.10s
Any suggestions?
I cannot find a PropertyChanged event handler and a call to it in your code. Add the INotifyPropertyChanged to your DataContext object and it should work
Personally I would model the visibility as a bool and use the BooleasnToVisibility Converter that comes with WPF.
Change string to Visibility
public Visibility UpDownButtonVisibility { get; set; }
this.UpDownButtonVisibility = Visibility.Collapsed;
Add INPC and binding to a View model.
Here is working sample:
XAML
<Window x:Class="ItemsControlDataTemplate.MainWindow"
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:ItemsControlDataTemplate"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<ItemsControl x:Name="Targets" Margin="0,4,0,4" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,5,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox TextWrapping="Wrap" Foreground="{Binding Foreground, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Address, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" Tag="{Binding}" />
<Button Margin="2,0,0,0" Grid.Column="1" Content=" ↑ " Visibility="Visible" />
<Button Margin="2,0,0,0" Grid.Column="2" Content=" ↓ " Visibility="{Binding Path=UpDownButtonVisibility}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
C#
class MainWindowViewModel : INotifyPropertyChanged
{
ObservableCollection<MapVM> _items = new ObservableCollection<MapVM>();
public ObservableCollection<MapVM> Items { get { return _items; } }
public MainWindowViewModel()
{
Items.Add(new MapVM() { UpDownButtonVisibility = Visibility.Visible, Address = "1111111" });
Items.Add(new MapVM() { UpDownButtonVisibility = Visibility.Collapsed, Address = "222222" });
Items.Add(new MapVM() { UpDownButtonVisibility = Visibility.Visible, Address = "33333333" });
}
public event PropertyChangedEventHandler PropertyChanged;
}
class MapVM : INotifyPropertyChanged
{
public MapVM()
{
this.UpDownButtonVisibility = Visibility.Collapsed;
this.TargetButtonVisibility = Visibility.Collapsed;
}
private Visibility _upDownButtonVisibility;
public Visibility UpDownButtonVisibility
{
get { return _upDownButtonVisibility; }
set
{
_upDownButtonVisibility = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UpDownButtonVisibility)));
}
}
private Visibility _targetButtonVisibility;
public Visibility TargetButtonVisibility
{
get { return _targetButtonVisibility; }
set
{
_targetButtonVisibility = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TargetButtonVisibility)));
}
}
private string _address;
public string Address
{
get { return _address; }
set
{
_address = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Address)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
The DataContext of your TargetButtonVisibility binding is your main MapVM. This works ok.
The DataContext within your DataTemplate is not MapVM, is it the item being displayed by the template.
Since you have not supplied any ItemsSource binding on your ItemsControl we have no way of knowing what this actually is.
Also, as unkreativ points out, do not use Visibility in your VM, since this is a view related type. Use bool instead and call the property IsUpDownButtonVisible or similar.
EDIT: Assuming you do actually want to bind to your single MapVM, you could use a RelativeSource to find the parent ItemsControl:
<Button Margin="2,0,0,0"
Grid.Column="2"
Content=" ↓ "
Click="MoveLocationDown"
Visibility="{Binding DataContext.UpDownButtonVisibility,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemsControl}}"/>
However, since you've already named your ItemsControl (Targets), you can simply refer to it by name:
<Button Margin="2,0,0,0"
Grid.Column="2"
Content=" ↓ "
Click="MoveLocationDown"
Visibility="{Binding DataContext.UpDownButtonVisibility,
ElementName=Targets}"/>
I am Working on Windows phone 8.1.I Making Simple ListView Demo Which is Contain two Image And Two TextBlock.
<ListView x:Name="lst1" Grid.ColumnSpan="2" >
<ListView.ItemTemplate>
<DataTemplate >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Image x:Name="imgSender" Source="Assets/button_register.png" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="8,15,8,8" />
<TextBlock Text="{Binding Sender}" FontSize="18" Foreground="Black" FontWeight="Bold" HorizontalAlignment="Center" Margin="0,12,0,0"/>
<Image x:Name="imgReceiver" Source="Assets/button_register.png" Grid.Row="1" Stretch="None" VerticalAlignment="Top" Margin="4,0" />
<TextBlock Text="{Binding Receiver}" FontSize="18" Foreground="Black" FontWeight="Bold" HorizontalAlignment="Center" Grid.Row="1" VerticalAlignment="Top"/>
<Image Source="Assets/scroll_line_addcategory.png" Grid.Row="2" Stretch="None" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="4,8,4,0" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And My Problem is That I Want to Set the Visibility of 'imgSender' at Runtime in Xaml.cs File.
Is Anybuddy Have Any Idea For Access UI Control Contain by Data Template.
<ListView x:Name="lst1" Grid.ColumnSpan="2" >
<ListView.ItemTemplate>
<DataTemplate >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Image Visibility="{Binding SenderVisibility}" x:Name="imgSender" Source="Assets/button_register.png" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="8,15,8,8" />
<TextBlock Text="{Binding Sender}" FontSize="18" Foreground="Black" FontWeight="Bold" HorizontalAlignment="Center" Margin="0,12,0,0"/>
<Image x:Name="imgReceiver" Source="Assets/button_register.png" Grid.Row="1" Stretch="None" VerticalAlignment="Top" Margin="4,0" />
<TextBlock Text="{Binding Receiver}" FontSize="18" Foreground="Black" FontWeight="Bold" HorizontalAlignment="Center" Grid.Row="1" VerticalAlignment="Top"/>
<Image Source="Assets/scroll_line_addcategory.png" Grid.Row="2" Stretch="None" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="4,8,4,0" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And my Model Class is
public class User : INotifyPropertyChanged
{
private string sender = string.Empty;
private string receiver = string.Empty;
private string senderVisibility = string.Empty;
private string receiverVisibility = string.Empty;
public string Sender
{
get
{
return sender;
}
set
{
if (value != this.sender)
{
this.sender = value;
NotifyPropertyChanged("Sender");
}
}
}
public string Receiver
{
get
{
return this.receiver;
}
set
{
if (value != this.receiver)
{
this.receiver = value;
NotifyPropertyChanged("Receiver");
}
}
}
public string ReceiverVisibility
{
get
{
return this.receiverVisibility;
}
set
{
if (value != this.receiverVisibility)
{
this.receiverVisibility = value;
NotifyPropertyChanged("ReceiverVisibility");
}
}
}
public string SenderVisibility
{
get
{
return this.senderVisibility;
}
set
{
if (value != this.senderVisibility)
{
this.senderVisibility = value;
NotifyPropertyChanged("SenderVisibility");
}
}
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
If your ListView contains only one item you can easily subscribe to Loaded event of imgSender and then control it's Visibility.
private Image _imgSender;
void Image_Loaded(object sender, RoutedEventArgs e)
{
_imgSender = sender as Image();
}
void AnotherMethod()
{
if (_imgSender != null)
_imgSender.Visibility = Visibility.Collapsed;
}
EDIT
FOR MULTIPLE ITEMS
void HideImage(int elementIndex)
{
var container = lst1.ContainerFromIndex(elementIndex) as ListViewItem;
var imageSender = (container.Content as Grid).Children[0] as Image;
imageSender.Visibility = Visibility.Collapsed;
}
You can add a BooleanToVisibilityConverter
<Page.Resources>
<converter:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Page.Resources>
<ListView x:Name="lst1" Grid.ColumnSpan="2"
ItemsSource="{Binding TelecomCollection}">
<ListView.ItemTemplate>
<DataTemplate >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Image x:Name="imgSender" Source="Assets/button_register.png" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="8,15,8,8"
Visibility="{Binding ShowImgReceiver, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock Text="{Binding Sender}" FontSize="18" Foreground="Black" FontWeight="Bold" HorizontalAlignment="Center" Margin="0,12,0,0"/>
<Image x:Name="imgReceiver" Source="Assets/button_register.png" Grid.Row="1" Stretch="None" VerticalAlignment="Top" Margin="4,0" />
<TextBlock Text="{Binding Receiver}" FontSize="18" Foreground="Black" FontWeight="Bold" HorizontalAlignment="Center" Grid.Row="1" VerticalAlignment="Top"/>
<Image Source="Assets/scroll_line_addcategory.png" Grid.Row="2" Stretch="None" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="4,8,4,0" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Then in your item class you can add ShowImgReceiver
public class Telecom
{
string Receiver {get; set;}
string Sender {get; set;}
bool ShowImgReceiver{get; set;}
}
In your binding class
public class ViewModel
{
public ObservableCollection<Telecom> TelecomCollection {get; set;}
}
If you want to make an item's image visibility Collapsed, then
TelecomCollection[0].ShowImgReceiver = false;
and to make it again Visible
TelecomCollection[0].ShowImgReceiver = true;
I am just doing some practice on WPF & MVVM forms
So far, I have just completed the normal Listbox with data loaded from an array, that when the index changes, it loads other data into textblocks on the form
I would like to change this example however. I want the user to select a field in the listbox, then click a button to display all the other data
So, the form is (usercontrol):
And when someone selects, say Cena, they will see the data on the right filled out.
My code for the .xaml is:
<UserControl x:Class="SuperstarsRoster.RosterView"
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"
xmlns:local="clr-namespace:SuperstarsRoster"
xmlns:WpfToolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="600">
<UserControl.DataContext>
<local:RosterViewModel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!--SelectedItem="{Binding SelectedPerson}"-->
<ListBox Grid.Column="0" Margin="0"
ItemsSource="{Binding RosterList}"
DisplayMemberPath="Name"
Name="lstRoster"
Height="250"
VerticalAlignment="Top"/>
<Button Content="Exit" Height="23" HorizontalAlignment="Left" VerticalAlignment="Bottom" Name="btnExit" Width="150" Command="{Binding ButtonCommand}" CommandParameter="Hai" />
<Grid x:Name="PersonDetails" Grid.Column="1" DataContext="{Binding SelectedPerson}" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.ColumnSpan="2" Text="Person Details" FontSize="15"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Name"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Name,Mode=TwoWay}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Brand"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Brand,Mode=TwoWay}"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Type"/>
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding Type,Mode=TwoWay}"/>
<Button Grid.Row="4" Content="Choose" Height="23" HorizontalAlignment="Left" VerticalAlignment="Bottom" Name="btnChoose" Width="90" Command="{Binding ChooseCommand}" CommandParameter="{Binding ElementName=lstRoster,Path=SelectedPerson}" />
</Grid>
</Grid>
my problem is the Show button:
<Button Grid.Row="4" Content="Choose" Height="23" HorizontalAlignment="Left" VerticalAlignment="Bottom" Name="btnChoose" Width="90" Command="{Binding ChooseCommand}" CommandParameter="{Binding ElementName=lstRoster,Path=SelectedPerson}" />
I don't know what to do here. I don't know if the CommandParameters should be empty, or have something in it.
My ViewModel code is:
#region Show Button
private ICommand m_ShowCommand;
public ICommand ShowCommand
{
get
{
return m_ShowCommand;
}
set
{
m_ShowCommand = value;
}
}
public void Selected(object obj)
{
RaisePropertyChanged("SelectedPerson");
}
#endregion
I have a feeling my Selected is the incorrect piece of code. On the ViewModel code there is also:
#region Roster Details
public ObservableCollection<Roster> rosterList;
public Roster selectedPerson;
public RosterViewModel()
{
ButtonCommand = new RelayCommand(new Action<object>(ShowMessage));
ShowCommand = new RelayCommand(new Action<object>(Selected));
rosterList = new ObservableCollection<Roster>()
{
new Roster(){Name="Batista", Brand="Smackdown!", Type="Heel"},
new Roster(){Name="The Rock", Brand="RAW", Type="Face"},
new Roster(){Name="John Cena", Brand="RAW", Type="Face"},
new Roster(){Name="Randy Orton", Brand="Smackdown!", Type="Heel"}
};
RaisePropertyChanged("SelectedPerson");
}
#endregion
With the ShowCommand being the button event, but I don't know what to put here.
Ideally, user selects Cena, clicks show, and the textblocks will fill with the data from the array.
My RelayCommand class (the default for all button clicks) looks like so:
class RelayCommand : ICommand
{
private Action<object> _action;
public RelayCommand(Action<object> action)
{
_action = action;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action(parameter);
}
#endregion
Should there be something more here? Or in the ViewModel?
I'm using MVVM, so there is no code in my code behind class.
The Exit button works, that's why I know the commands will work.
EDIT
I changed some of the code now. Firstly, the show button was sitting in a grid that had a datacontext setting. That was taken out. Next, both buttons share the same command (ButtonCommand), but both have different parameters. Exit is "Exit", and choose is "Hai"
In the ViewModel code, the following was added and changed
public string Name { get; set; }
public string Brand { get; set; }
public string Type { get; set; }
#region Button Work
private ICommand m_ButtonCommand;
public ICommand ButtonCommand
{
get
{
return m_ButtonCommand;
}
set
{
m_ButtonCommand = value;
}
}
public void DoAction(object obj)
{
if (obj.ToString() == "Hai")
{
if (selectedPerson != null)
{
this.Name = selectedPerson.Name;
this.Brand = selectedPerson.Brand;
this.Type = selectedPerson.Type;
RaisePropertyChanged(null);
}
}
if (obj.ToString() == "Exit")
{
MessageBox.Show("This program will now close");
Environment.Exit(0);
}
}
#endregion
This way, data is set and I can populate the fields when wanted.
Along with initializing it first
public RosterViewModel()
{
ButtonCommand = new RelayCommand(new Action<object>(DoAction));
So, it works now.
You have a couple of choices really:
(1) Have a VM property that is bound to the SelectedItem of your ListBox, and then you don't need to send any parameters with your button command ... you can just perform your ChooseCommand on the currently SelectedItem (as known by you VM).
or
(2) Pass in the SelectedItem as a parameter on your command, like this ...
<Button Grid.Row="4" Content="Choose" Height="23" HorizontalAlignment="Left"
VerticalAlignment="Bottom" Name="btnChoose" Width="90"
Command="{Binding ChooseCommand}"
CommandParameter="{Binding ElementName=MyListBox,Path=SelectedItem}" />
Note that you will need to give your ListBox a name (x:Name="MyListBox") and that your CommandParameter is referring to that ListBox, and its SelectedItem property.