So I'm having what should be a simple XAML Data Binding error. I've got the below XAML with two classes (so far) that they used for DataBinding, a Row, which contains an ObservableCollection rows. The nodes have a bunch of extra associated information and I'm trying to show these nodes in a grid-like fashion (it's going to be used for a path-finding algorithm.)
The problem is that the "Here" TextBlock doesn't show up. But I know that the Nodes are getting bound properly because their values show up in the Debugging StackPanel.
<Window.Resources>
<local:Row x:Key="TestRow">
<local:Node x="0" y="0" Cost="20" />
<local:Node x="0" y="1" Cost="20" />
<local:Node x="0" y="2" Cost="20" />
</local:Row>
</Window.Resources>
<Grid Name="MainGrid" Margin="10,10,10,10" >
<Grid.RowDefinitions>
<RowDefinition Height="150" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Margin="0,25,0,0"
DataContext="{Binding ElementName=AStarListView,
Path=SelectedItem}"
x:Name="Debugging" Orientation="Vertical" >
<TextBlock Text="{Binding x}" />
<TextBlock Text="{Binding y}" />
<TextBlock Text="{Binding Cost}" />
</StackPanel>
<ListView Grid.RowSpan="2" Margin="2,2,2,2" Grid.Column="1"
x:Name="AStarListView"
ItemsSource="{StaticResource TestRow}" >
<ListView.ItemTemplate>
<DataTemplate DataType="local:Row">
<ListView ItemsSource="{Binding nodes}" Width="48" Height="48" >
<ListView.ItemTemplate>
<DataTemplate DataType="local:Node">
<TextBlock Text="Here" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here's the (stripped) Node class.
public class Node : INotifyPropertyChanged
{
public Tuple<int, int> coordinates { get; set; }
public int x
{
get
{
if (this.coordinates == null)
return -1;
return this.coordinates.Item1;
}
set
{
if (this.coordinates != null)
this.coordinates = new Tuple<int, int>(value, y);
else
this.coordinates = new Tuple<int, int>(value, -1);
OnPropertyChanged("x");
}
}
public int y
{
get
{
if (this.coordinates == null)
return -1;
return this.coordinates.Item2;
}
set
{
if (this.coordinates != null)
this.coordinates = new Tuple<int, int>(x, value);
else
this.coordinates = new Tuple<int, int>(-1, value);
OnPropertyChanged("y");
}
}
private Node _parent;
private int _Cost;
public int Cost
{
get
{
return _Cost;
}
set
{
_Cost = value;
OnPropertyChanged("Cost");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChangedEventArgs args =
new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, args);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Here's the Row class:
public class Row : ObservableCollection<Node>
{
public ObservableCollection<Node> nodes { get; set; }
public Row()
{
this.nodes = new ObservableCollection<Node>();
}
}
Here's the corrected XAML, the class definitions are correct in the question, need to replace "AStarListView" with this XAML.
<ListView Grid.RowSpan="2" Margin="2,2,2,2" Grid.Column="1" x:Name="AStarListView"
ItemsSource="{StaticResource TestRow}" >
<ListView.ItemTemplate>
<DataTemplate DataType="local:Node">
<Grid Background="#dddddd" >
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding x}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding y}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding Cost}"/>
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="24"/>
</Grid.ColumnDefinitions>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My problem was that I had the ListViews too deeply nested. The inner ListView was binding to the "nodes" property of a Node, which didn't exist.
Related
I have created a user control that essentially contains a bunch of comboboxes, called SearchParamsControl. The SearchParamsControl contains all the UI elements required to set everything in a SearchParams class:
My SearchParamsControl:
/// <summary>
/// Interaction logic for SearchParamsControl.xaml
/// </summary>
public partial class SearchParamsControl : INotifyPropertyChanged
{
public SearchParamsControl()
{
InitializeComponent();
}
/// <summary>
/// Radius entries bound to combobox
/// </summary>
public Dictionary<double, string> RadiusEntries
{
get;
set;
} = new Dictionary<double, string>()
{
{0, "This area only" },
{ 0.25, "Within 1/4 mile" },
{ 0.5, "Within 1/2 mile" },
{ 1, "Within 1 mile" },
{ 3, "Within 3 miles" },
{ 5, "Within 5 miles" },
{ 10, "Within 10 miles" },
{ 15, "Within 15 miles" },
{ 20, "Within 20 miles" },
{ 30, "Within 30 miles" },
{ 40, "Within 40 miles" }
};
public Dictionary<PropertyTypeEnum, string> PropertyTypes => PropertyTypeDictionary;
/// <summary>
/// Prices bound to combo box
/// </summary>
public List<int> Prices
{
get;
set;
} = new List<int>()
{
0,
50000,
60000,
70000,
80000,
90000,
100000,
110000,
120000,
125000,
130000,
150000,
200000,
250000,
300000,
325000,
375000,
400000,
425000,
450000,
475000,
500000,
550000,
600000,
650000,
700000,
800000,
900000,
1000000,
1250000,
1500000,
1750000,
2000000,
2500000,
3000000,
4000000,
5000000,
7500000,
10000000,
15000000,
20000000
};
/// <summary>
/// Bedrooms bound to combobox
/// </summary>
public List<int> Bedrooms
{
get;
set;
} = new List<int>()
{
0,
1,
2,
3,
4,
5
};
public StringTrieSet SearchString
{
get
{
return RightMoveCodes.RegionTree;
}
}
public double Radius
{
get => SearchParams.Radius;
set
{
if (SearchParams.Radius != value)
{
SearchParams.Radius = value;
OnSearchParamsChanged();
}
}
}
public int MinBedrooms
{
get { return SearchParams.MinBedrooms; }
set
{
if (SearchParams.MinBedrooms != value)
{
SearchParams.MinBedrooms = value;
OnSearchParamsChanged();
}
}
}
public int MaxBedrooms
{
get { return SearchParams.MaxBedrooms; }
set
{
if (SearchParams.MaxBedrooms != value)
{
SearchParams.MaxBedrooms = value;
OnSearchParamsChanged();
}
}
}
public int MinPrice
{
get { return SearchParams.MinPrice; }
set
{
if (SearchParams.MinPrice != value)
{
SearchParams.MinPrice = value;
OnSearchParamsChanged();
}
}
}
public int MaxPrice
{
get { return SearchParams.MaxPrice; }
set
{
if (SearchParams.MaxPrice != value)
{
SearchParams.MaxPrice = value;
OnSearchParamsChanged();
}
}
}
public SortType SortType
{
get { return SearchParams.Sort; }
set
{
if (SearchParams.Sort != value)
{
SearchParams.Sort = value;
OnSearchParamsChanged();
}
}
}
public SearchParams SearchParams
{
get
{
SearchParams searchParams = (SearchParams)GetValue(SearchParamsProperty);
return searchParams;
}
set
{
SetValue(SearchParamsProperty, value);
}
}
// Using a DependencyProperty as the backing store for MySelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SearchParamsProperty =
DependencyProperty.Register("SearchParams", typeof(SearchParams), typeof(SearchParamsControl), new PropertyMetadata(new SearchParams(), OnSearchParamsPropertyChanged));
public event PropertyChangedEventHandler PropertyChanged;
private static void OnSearchParamsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SearchParamsControl c = d as SearchParamsControl;
if (c != null)
{
c.OnSearchParamsChanged();
}
}
private void OnSearchParamsChanged()
{
OnPropertyChanged(nameof(SearchParams));
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And the xml:
<UserControl x:Class="RightMoveApp.UserControls.SearchParamsControl"
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:RightMoveApp.UserControls"
xmlns:System="clr-namespace:System;assembly=System.Runtime"
xmlns:StyleAlias="clr-namespace:RightMove;assembly=RightMove"
xmlns:viewModel="clr-namespace:RightMoveApp.ViewModel"
xmlns:dataTypes="clr-namespace:RightMove.DataTypes;assembly=RightMove"
xmlns:valueconverters="clr-namespace:RightMoveApp.UserControls.ValueConverters"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:Name="uc">
<UserControl.DataContext>
<viewModel:SearchParamsControlViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="dataTypes:SortType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<valueconverters:PropertyTypeConverter x:Key="PropertyTypeConverter" x:Shared="False"/>
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ComboStyle}">
<Setter Property="Margin" Value="0,0,0,1"/>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="Search area"/>
<local:AutoCompleteComboBox Grid.Row="0" Grid.Column="1"
ItemsSource="{Binding ElementName=uc, Path=SearchString}"
SelectedValue="{Binding Path=RegionLocation, Mode=TwoWay}"/>
<Label Grid.Column="0" Grid.Row="1" Content="Search radius"/>
<ComboBox Template="{DynamicResource ComboBoxTemplate1}" Grid.Column="1" Grid.Row="1" Name="comboSearchRadius"
ItemsSource="{Binding ElementName=uc, Path=RadiusEntries}"
SelectedValuePath="Key"
SelectedValue="{Binding ElementName=uc, Path=Radius, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Value}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Grid.Column="0" Grid.Row="2" Content="Price range (£)"/>
<Grid Grid.Column="1" Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ComboBox Grid.Column="0" Grid.Row="0" Name="comboMinPrice"
ItemsSource="{Binding ElementName=uc, Path=Prices}"
SelectedItem="{Binding ElementName=uc, Path=MinPrice, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Grid.Column="1" Grid.Row="0" Content="to"/>
<ComboBox Grid.Column="2" Grid.Row="0" Name="comboMaxPrice"
ItemsSource="{Binding ElementName=uc, Path=Prices}"
SelectedItem="{Binding ElementName=uc, Path=MaxPrice}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<Label Grid.Column="0" Grid.Row="3" Content="No. of bedrooms"/>
<Grid Grid.Column="1" Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ComboBox Grid.Column="0" Grid.Row="0" Name="comboMinBedrooms"
ItemsSource="{Binding ElementName=uc, Path=Bedrooms}"
SelectedItem="{Binding ElementName=uc, Path=MinBedrooms, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox Grid.Column="3" Grid.Row="0" Name="comboMaxBedrooms"
ItemsSource="{Binding ElementName=uc, Path=Bedrooms}"
SelectedItem="{Binding ElementName=uc, Path=MaxBedrooms, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Grid.Column="1" Grid.Row="0" Content="to"/>
</Grid>
<Label Grid.Column="0" Grid.Row="4" Content="Sort Type"/>
<ComboBox Grid.Column="1" Grid.Row="4" Name="comboSort"
ItemsSource="{Binding Source={StaticResource dataFromEnum}}"
SelectedItem="{Binding ElementName=uc, Path=SortType, Mode=TwoWay}">
</ComboBox>
</Grid>
I want to bind to the SearchParams dependency property in my MainWindow, which contains the SearchParamsControl:
<GroupBox Grid.Column="0" Grid.Row="0" Header="Search Params" Panel.ZIndex="10">
<controls:SearchParamsControl x:Name="searchControl"
SearchParams="{Binding Path=SearchParams, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding Path=IsSearching, Converter={StaticResource BooleanToReverseConverter}}">
</controls:SearchParamsControl>
</GroupBox>
<Button x:Name="btnSearch" Grid.Column="0" Grid.Row="1"
Content="Search"
IsDefault="True"
Command="{Binding SearchAsyncCommand}"/>
This binding works fine. But, as you can see, I've got a Button in my MainWindow, which is hooked up to a command. I want to know when a property in SearchParams has changed, so that I can called something like SearchCommandAsync.RaiseCanExecuteChanged() so that I can update the state of the Search button. How can I handle this?
Note that I don't want to use the following commented out code (from my Command class), which I think does work, but rather I want to be able to notify that the SearchParams has a changed property and therefore we need to call CanExecute again:
//public event EventHandler CanExecuteChanged
//{
// add
// {
// CommandManager.RequerySuggested += value;
// }
// remove
// {
// CommandManager.RequerySuggested -= value;
// }
//}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
//CommandManager.InvalidateRequerySuggested();
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, new EventArgs());
}
}
Also I don't want to modify SearchParams with a PropertyChanged event handler (imagine it is a closed off class library and I can't modify it).
If I understand your setup correctly, the SearchParams property of the SearchParamsControl on the view should set the data-bound SearchParams source property of the view model whenever the control property changes.
You can then raise the CanExecuteChanged method of the command in the setter of the SearchParams source property.
I am trying to make a button in a viewmodel recognize that a radio button in another view model (a UserControl that is activated on the first viewmodel) has been selected, thus enabling the button in the first viewmodel.
I have a UserControl AlbumsDisplayViewModel within my base view model BaseViewModel.
In BaseView XAML There's a button (OpenAlbum) which is supposed to be enabled when a radio button on the AlbumsDisplayView is selected (see CanOpenAlbum).
BaseView XAML:
<Canvas x:Name="cnvsInputWrapper" Background="LightGray"
Grid.Column="4" Grid.Row="1" Grid.RowSpan="4"
Margin="5">
<Canvas.OpacityMask>
<VisualBrush Visual="{Binding ElementName=maskRoundEdges}" />
</Canvas.OpacityMask>
<DockPanel Margin="15, 25">
<ContentControl x:Name="ActiveItem" />
</DockPanel>
</Canvas>
<!-- Action Buttons section -->
<Grid Grid.Row="3" Grid.Column="1" Grid.RowSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="12" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="12" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="12" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
<RowDefinition Height="12" />
</Grid.RowDefinitions>
<Button Grid.Row="1" Grid.Column="1" x:Name="OpenAlbum"
IsEnabled="{Binding CanOpenAlbum}">
<StackPanel Orientation="Vertical">
<TextBlock>Open</TextBlock>
<TextBlock>Album</TextBlock>
</StackPanel>
</Button>
</Grid>
BaseViewModel C#:
public class BaseViewModel : Conductor<object>
{
private AlbumsDisplayViewModel m_vmAlbumsDisplay; // Initialized in another function.
public BaseViewModel()
{
}
public bool CanOpenAlbum() => (m_vmAlbumsDisplay != null) && (m_vmAlbumsDisplay.GetSelectedAlbum() != null);
public void OpenAlbum()
{
AlbumModel album = m_vmAlbumsDisplay.GetSelectedAlbum();
//ActivateItem(/*albumViewModel(album)*/);
}
}
AlbumsDisplayView XAML:
<ItemsControl x:Name="Albums" FlowDirection="LeftToRight"
Margin="10, 0">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vms:AlbumViewModel}">
<StackPanel Orientation="Horizontal" Margin="0, 5">
<RadioButton GroupName="rdbtnAlbums"
IsChecked="{Binding IsSelected}" />
<!-- Album Details -->
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
AlbumsDisplayViewModel C#:
class AlbumsDisplayViewModel : Screen
{
private ObservableCollection<AlbumViewModel> m_albums;
public AlbumsDisplayViewModel()
{
}
public ObservableCollection<AlbumViewModel> Albums
{
get { return m_albums; }
set
{
m_albums = value;
NotifyOfPropertyChange(() => Albums);
}
}
public AlbumModel GetSelectedAlbum()
{
AlbumModel res = null;
foreach (var vmAlbum in Albums)
{
if (vmAlbum.IsSelected)
{
res = vmAlbum.Album;
break;
}
}
return res;
}
}
And last, AlbumViewModel C#:
class AlbumViewModel : Screen
{
private AlbumModel m_albumModel;
private bool m_isSelected;
public AlbumViewModel(AlbumModel albumModel)
{
m_albumModel = albumModel;
}
public AlbumModel Album
{
get { return m_albumModel; }
set
{
m_albumModel = value;
NotifyOfPropertyChange(() => Album);
}
}
public bool IsSelected
{
get { return m_isSelected; }
set
{
m_isSelected = value;
NotifyOfPropertyChange(() => IsSelected);
}
}
}
I expected that when IsSelected in AlbumViewModel is changed (when the user selects a radio button), the OpenAlbum button will be enabled, because CanOpenAlbum will return true, but I have realized that CanOpenAlbum wasn't even called for some reason. What do I need to do so CanOpenAlbum will be notified to be called whenever a radio button is selected?
After searching for an answer for a long time, I decided that it will be better to search for a better solution, instead of an answer. I've discovered that a ListBox element has a SelectedItem property, which eliminates the need for radio buttons.
Eventually, I replaced the ItemsControl with a ListBox, and I;m happy with the results.
AlbumDisplayView XAML (only this has changed):
<ScrollViewer x:Name="Scroller" Height="300"
FlowDirection="RightToLeft">
<ListBox x:Name="Albums" FlowDirection="LeftToRight"
Background="Transparent" Margin="10, 0"
BorderThickness="0" SelectedItem="{Binding SelectedAlbum}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vms:AlbumViewModel}">
<StackPanel Orientation="Horizontal" Margin="0, 5">
<!-- Album Details -->
<StackPanel Orientation="Vertical">
<StackPanel Grid.Row="1" Grid.Column="1"
Orientation="Horizontal" Margin="12, 0">
<TextBlock Text="{Binding Album.Name}" />
<TextBlock Text=" - User#" />
<TextBlock Text="{Binding Album.OwnerID}" />
</StackPanel>
<TextBlock Text="{Binding Album.CreationDate}"
FontSize="12" Margin="12, 0" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
</ScrollViewer>
I have a counter which increments depending on a foreach Loop from:
public partial class UserControlCounter : UserControl, INotifyPropertyChanged
{
private int _scanStatusCounter;
public int ScanStatusCounter
{
get { return _scanStatusCounter; }
set { _scanStatusCounter = value; NotifyPropertyChanged(); }
}
public UserControlCounter()
{
InitializeComponent();
DataContext = this;
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
await Task.Run(getAll);
scanStatus.Text = "Persons " + ScanStatusCounter.ToString();
}
private async void getAll()
{
//grab data and iterate it
string[] string_array = new string[] { "a", "b", "c", "d", "e" }; // type = System.String[]
foreach (var i in string_array)
{
ScanStatusCounter++;
await Task.Delay(100);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
and the Xaml:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Grid.Column="0" Name="myListBox" Height="40" ></ListBox>
<TextBlock Grid.Row="0" Grid.Column="1" Name="scanStatus" Height="40" Text="{Binding Path=ScanStatusCounter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBlock>
<Button Grid.Row="0" Grid.Column="2" Click="Button_Click" Height="40">Click Me</Button>
</Grid>
That works fine and increments "live", but my problem is that I need to get that now running inside a more complex Data Template.
I've tried to inject the Counter onClick with:
private async void Button_Click(object sender, RoutedEventArgs e)
{
var btn = sender as Button;
var contentPresenter = (btn.TemplatedParent as ContentPresenter);
var ppStatusCounter = contentPresenter.ContentTemplate.FindName("scanStatus", contentPresenter) as TextBlock;
ppStatusCounter.Text = "Entrys found: " + ScanStatusCounter.ToString();
}
But then I get the Value only onClick not like a live incrementing Counter, that's my Data Template:
<UserControl.Resources>
<c1:NameList x:Key="NameListData"/>
<DataTemplate x:Key="NameItemTemplate">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Domaine"/>
<TextBox Grid.Row="1" Grid.Column="0" x:Name="txtDomainName" Text="{Binding Path=DomaineName}" Margin="0,0,5,0"></TextBox>
<Button Grid.Row="1" Grid.Column="1" Width="100" Height="30" Margin="5,5,5,5" x:Name="btnNames" Click="Button_Click" Content="Start Scan" />
<Grid Grid.Column="2" Grid.Row="1" Height="20">
<ProgressBar x:Name="pbStatus" Height="20" Width="100" Minimum="0" Maximum="100" Visibility="Hidden"/>
<TextBlock x:Name="pbStatusText" HorizontalAlignment="Center" VerticalAlignment="Center" Text="Scanning" Visibility="Hidden"/>
</Grid>
<TextBlock Grid.Column="3" Grid.Row="1" Name="scanStatus" Text="{Binding Path=ScanStatusCounter, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Horizontal" VerticalAlignment="top">
<StackPanel Orientation="Horizontal" Margin="0,20,0,0">
<ListBox x:Name="lstDomainNames"
Margin="5,5,5,5"
ItemsSource="{Binding Source={StaticResource NameListData}}"
ItemTemplate="{StaticResource NameItemTemplate}"
IsSynchronizedWithCurrentItem="True"/>
</StackPanel>
</StackPanel>
</Grid>
that's my Data Source Class, not much there:
public partial class NameList : ObservableCollection<SetCredentials>
{
private static Logger logger = LogManager.GetCurrentClassLogger();
public NameList() : base()
{
using var forest = Forest.GetCurrentForest();
Forest currentForest = Forest.GetCurrentForest();
DomainCollection domains = currentForest.Domains;
foreach (Domain objDomain in domains)
{
Add(new SetCredentials(objDomain.ToString()));
}
}
}
public class SetCredentials
{
private string domainName;
public SetCredentials(string domain)
{
this.domainName = domain;
}
public string DomaineName
{
get { return domainName; }
set { domainName = value; }
}
}
Do I have to add a second Data Binding source to my ItemSource or do I need another approach for this, for example in my TextBox Binding?
Is there any way to get the index of the data which is binded to the ItemsControl, when a button is clicked?
<ItemsControl x:Name="ic_schranke" DataContext="{Binding Schranke}" >
<ItemsControl.ItemTemplate>
<DataTemplate >
<Button Height="80" x:Name="btn_open" Click="btn_open_Click" HorizontalAlignment="Stretch" Style="{StaticResource TransparentStyle}">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="30*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Rectangle x:Name="rect" Grid.ColumnSpan="3" Grid.Row="1" Opacity="0.65" Grid.Column="0" Fill="#FFCEEAFF"/>
<Border CornerRadius="25" Height="50" Width="50" HorizontalAlignment="Center" Grid.Column="0" Grid.Row="1">
<Border.Background>
<ImageBrush ImageSource="/Assets/schranken.jpg" />
</Border.Background>
</Border>
<TextBlock Name="schranken_name" Grid.Column="1" Grid.Row="1" Text="{Binding name}" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="ExtraLight" Foreground="Black" />
</Grid>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the sample data which is binded to the IC:
List<Schranke> elements;
public UserPage()
{
this.InitializeComponent();
elements = new List<Schranke>();
for (var i = 1; i < 15; i++)
elements.Add(new Schranke() { name = $"Schranke NR:{i}" });
this.ic_schranke.ItemsSource = elements;
}
And here is the code in the button click event:
private async void btn_open_Click(object sender, RoutedEventArgs e)
{
Grid grid = (sender as Button).Content as Grid;
//Debug.WriteLine($"content {tb.Text} clicked");
var tmp_background = grid.Background;
grid.Background = new SolidColorBrush(new Windows.UI.Color() { A = 255, R = 78, G = 170, B = 44 });
VibrationDevice testVibrationDevice = VibrationDevice.GetDefault();
testVibrationDevice.Vibrate(System.TimeSpan.FromMilliseconds(150));
await Task.Delay(3000);
grid.Background = tmp_background;
}
Maybe something along the lines of this:
the presenter - put this in the data context
public class SchrankePresenter : INotifyPropertyChanged
{
private List<Schranke> _elements;
public List<Schranke> Elements
{
get { return _elements; }
set
{
_elements = value;
OnPropertyChanged("Elements");
}
}
public ICommand ClickCommand { get; set; }
private void OnPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
public SchrankePresenter()
{
var elements = new List<Schranke>();
for (var i = 1; i < 15; i++)
elements.Add(new Schranke() { Name = $"Schranke NR:{i}" });
Elements = elements;
ClickCommand = new DelegateCommand(ClickAction);
}
public void ClickAction(Schranke item)
{
VibrationDevice.GetDefault().Vibrate(TimeSpan.FromMilliseconds(150));
}
}
public class Schranke
{
public string Name { get; set; }
}
the template:
<ListView ItemsSource="{Binding Elements}">
<ListView.ItemTemplate>
<DataTemplate>
<Button Height="80"
HorizontalAlignment="Stretch"
Command="{Binding ClickCommand}"
Style="{StaticResource TransparentStyle}">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="30*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Rectangle x:Name="rect"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
Fill="#FFCEEAFF"
Opacity="0.65" />
<Border Grid.Row="1"
Grid.Column="0"
Width="50"
Height="50"
HorizontalAlignment="Center"
CornerRadius="25">
<Border.Background>
<ImageBrush ImageSource="/Assets/schranken.jpg" />
</Border.Background>
</Border>
<TextBlock Name="schranken_name"
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="ExtraLight"
Foreground="Black"
Text="{Binding Name}" />
</Grid>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
you can do the background animation using a storyboard/visualstate
EDIT : regarding the DelegateCommand, it's a simple implementation I use for my WPF apps -
internal class DelegateCommand<T> : ICommand
{
private Action<T> _clickAction;
public DelegateCommand(Action<T> clickAction)
{
_clickAction = clickAction;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_clickAction((T)parameter);
}
}
I am trying to bind a dictionary to two textblocks in a listview. The listview ItemsSource binding is defined in the code behind and the text blocks content is in the XAML.
I am able to display the items but they are displayed with square brackets around each row like [stringA, stringB]. However, this format will not work. The latest code that I tried was by setting the Key and Value which did not work was:
XAML:
<ListView Name="lvListLogs"
Margin="0,10,0,0">
<ListView.ItemTemplate>
<DataTemplate x:Name="ListItemTemplate">
<Grid Margin="5,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="122"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition MaxHeight="104"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="tb_PointName" Grid.Column="1"
Text="{Binding Key}"
Margin="10,0,0,0" FontSize="40"
TextWrapping="Wrap"
MaxHeight="72"
Foreground="#FFFE5815" />
<TextBlock x:Name="tb_PointValue" Grid.Column="1"
Text="{Binding Value}"
Margin="10,0,0,0" FontSize="40"
TextWrapping="Wrap"
MaxHeight="72"
Foreground="#FFFE5815" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
C# (abridged for clarity):
public Dictionary<string, string> mydict2 { get; set; }
mydict2 = new Dictionary<string, string>();
if (item != null)
{
var props = item.GetType().GetRuntimeProperties();
foreach (var prop in props)
{
foreach (var itm in group1.Items.Where(x => x.UniqueId == prop.Name))
{
var _Title = prop.Name;
var _Value = prop.GetValue(item, null);
string propertyValue;
string propertyName;
propertyValue = Convert.ToString(_Value);
propertyName = _Title;
mydict2.Add(_Title, propertyValue);
}
}
//binding here
lvListLogs.ItemsSource = mydict2;
}
Any assistance would be appreciated.
Your code works fine, the problem is you set the same Grid.Column for both TextBlocks. The first column index should be zero:
<TextBlock x:Name="tb_PointName" Grid.Column="0" ...
To achieve the required binding, instead of the Dictionary I used an ObservableCollection with the class and constructor.
To databind the listview (xaml) to ObservableCollection:
Create the Class with Constructor
public class PointInfoClass
{
public string PointName { get; set; }
public string PointValue { get; set; }
public PointInfoClass(string pointname, string pointvalue)
{
PointName = pointname;
PointValue = pointvalue;
}
}
Create collection of the PointInfoClass
public ObservableCollection<PointInfoClass> PointInfo
{
get
{
return returnPointInfo;
}
}
Instantiate the collection
ObservableCollection<PointInfoClass> returnPointInfo = new ObservableCollection<PointInfoClass>();
Add item to collection
returnPointInfo.Add(new PointInfoClass(string1, string2));
Databind to the ObservableCollection name.
The xaml code:
<ListView
Grid.Row="1"
ItemsSource="{Binding PointInfo}"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick"
Margin="19,0.5,22,-0.333"
x:Name="lvPointInfo"
Background="White">
<ListView.ItemTemplate>
<DataTemplate >
<Grid Margin="0,0,0,20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="270"/>
<ColumnDefinition Width="60"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical" Grid.Column="1" VerticalAlignment="Top">
<TextBlock x:Name="tb_PointSubTitle" Grid.Column="1"
Text="{Binding PointName}"
Margin="10,0,0,0" FontSize="20"
TextWrapping="Wrap"
MaxHeight="72"
Foreground="#FF5B5B5B"
/>
</StackPanel>
<StackPanel Orientation="Vertical" Grid.Column="2" VerticalAlignment="Top" HorizontalAlignment="Right">
<TextBlock x:Name="tb_PointValue"
Grid.Column="1"
Text="{Binding PointValue}"
Margin="0,5,0,0" FontSize="20"
HorizontalAlignment="Right"
TextWrapping="Wrap"
FontWeight="Normal"
Foreground="Black" />
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Set the DataContext of the ListView
lvPointInfo.DataContext = this;
This code is edited for clarity.