ItemTemplate for UserControls in ItemsControl - WPF - c#

My task is to implement a MDI-like interface in our WPF app.
I have created this simple class as a base for all the views:
public class BaseView : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private ViewType _type = ViewType.Null;
private string _tabTitle = string.Empty;
private bool _isSelected = false;
public ViewType Type { get => _type; set { _type = value; OnPropertyChanged(); } }
public string TabTitle { get => _tabTitle; set { _tabTitle = value; OnPropertyChanged(); } }
public bool IsSelected { get => _isSelected; set { _isSelected = value; OnPropertyChanged(); } }
}
Next, I created few test Views. All of them start like this:
<local:BaseView...
In main window, there are two controls: ItemsControl (for displaying the list of opened views), and ContentControl (for displaying the selected view.)
I store all the opened views in a ObservableCollection: ObservableCollection<BaseView>....
I wanted to display them as a list, so I created ItemsControl:
<ItemsControl x:Name="mainItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Padding="2" Margin="2" Tag="{Binding Type}">
<TextBlock Text="{Binding TabTitle}" Foreground="White"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
When I set the ItemsControl's source (mainItemsControl.ItemsSource = openedViews;) and started the application, ItemsControl displayed the content of each View instead of the ItemTemplate (Border with the TextBlock). What did I do wrong?

If I understood correctly, then the openedViews collection consists of BaseView.
If so, then BaseView is a UIElement.
But Data Templates are used to render non UIElements.
If the Content receives a UIElement, then it is rendered directly as is.
One possible variant solution.
You need to remove the INotifyPropertyChanged interface from BaseView.
Create a data source for your BaseView with an implementation of INotifyPropertyChanged.
In BaseView create DependencyProperty for this source.
Create a simple, helper container for the openedViews collection.
Something like this (pseudo code):
public class SomeContainer
{
public BaseDataSource DataSource
{
get => _dataSource;
set
{
_dataSource = null;
if(View is not null)
{
View.DataSource = DataSource;
}
}
}
public BaseView View
{
get => _view;
set
{
_view = value;
if(_view is not null)
{
_view.DataSource = DataSource;
}
}
}
}

Related

C# BindingList<> not updating a WPF Listbox on changed items

I am on a MVVM C# project.
I want to display a list of objects.
I want to add and remove items in this list and ALSO change items in this list.
So I choosed the BindingList<> over the ObservableCollection<>, which would not get noticed if an item has changed.
(I also tested the ObservableCollectionEx which is out there in the web, but this has the same behavior like the BindingList for me).
But the Listbox is not changing when items are changed.
(Adding and removing items is updated in the Listbox)
In my XAML
<ListBox DisplayMemberPath="NameIndex" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}">
or alternative with the ItemTemplate
<ListBox DockPanel.Dock="Right" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}" Margin="0,10,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding NameIndex}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In my ViewModel (ViewModelBase is implementing INotifyPropertyChanged etc)
public class ProfileListViewModel : ViewModelBase
{
private BindingList<Profile> profiles;
public BindingList<Profile> Profiles
{
get
{
return profiles;
}
set
{
profiles = value;
RaisePropertyChanged();
}
}
My items are also implementing INotifyPropertyChanged and I am calling OnPropertyChanged("Name") in my Setters.
My model
public class Profile : INotifyPropertyChanged
{
public Profile(){}
public int ProfileID { get; set; }
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Wiring the View with the ViewModel (BindingList is initialized before View)
ProfileListViewModel plvw= new ProfileListViewModel(message.Content);
var profileView = new ProfileListView(plvw);
profileView.ShowDialog();
In the View.xaml.cs
public ProfileListView(ProfileListViewModel plvw)
{
InitializeComponent();
DataContext = plvw;
}
When I am changing the name of an object then I get the ListChanged event to which I have subscribted in my ViewModel (Profiles.ListChanged += Profiles_ListChanged;) for testing BUT the items in the ListBox are NOT changing.
What am I doing wrong?
How can I get a updated Listbox?
Since your DisplayIndex is the computed property NameIndex, you need to call OnPropertyChanged("NameIndex") when its value changes due to a change in other properties, e.g.:
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
OnPropertyChanged("NameIndex");
}
}
Use
Profiles.ResetBindings() to bind it again.

string bound to TextBlock in ComboBox doesn't show up

I have a TextBlock in a ComboBox in a C# WPF project bound to a list of 'Envelope' items, which have a string 'Name' and a double 'Weight' property, the former of which I would like to see displayed in the TextBlock.
When I run my program, the ComboBox appears without any text in it. It properly has three unlabeled items in it, and if I view the ItemsSource or SelectedItem of the ComboBox they show the appropriate values, and other code which interacts with the SelectedItem of the ComboBox behaves properly. The only thing that does not work is that the TextBlock contains no text. If I replace the "{Binding Name}" with "au ghdfjlnvgmumar" then the appropriate garbled characters appear in the ComboBox, so it is definitely a problem with the binding. What is the problem, and how can I get around it?
Relevant code:
xaml:
<ComboBox Name="EnvelopeList" HorizontalAlignment="Center" Width="200" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
C#:
//main window code
public MainWindow()
{
InitializeComponent();
envelopes = new List<Envelope>();
envelopes.Add(new Envelope("TEST", 0));
envelopes.Add(new Envelope("HI", 10));
EnvelopeList.ItemsSource = envelopes;
}
//Envelope class
class Envelope
{
public string Name;
public double Weight;
public Envelope()
{
Name = "[None]";
Weight = 0;
}
public Envelope(string n, double w)
{
Name = n;
Weight = w;
}
public override string ToString()
{
return Name;
}
}
When DataBinding, you can only bind to Properties. Also, you need to update your properties with a PropertyChangedEvent. Otherwise, if you change your property after the initial binding it won't update the UI.
You need to use on property changed and a property
public class Envelope: ModelBase
{
private string _name;
public string Name
{
get { return _name; }
set { _name= value; OnPropertyChanged("Name"); }
}
}
public class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
}
Finally, I notice that you're setting ItemsSource directly. Instead, you want to set your View's DataContext property and then bind to your ItemsSource
Here is a MSDN article on DataBinding that will teach you how to do it properly.
Name is a field, you can only bind to properties.

Combo Box Binding Phone 8.1 UAP

How do i bind properly to a combox on windows phone 8.1 I tried what i would normally do in winforms but it didnt work. Also this is for a settings page is their any standard practise yet for a 8.1 Phone Store app to create a settings page same way silverlight did.
And before you ask yes the data is their fine have dubged that.
public class City
{
public string id { get; set; }
public string timing_title { get; set; }
}
public class CitysList
{
public List<City> cityList { get; set; }
}
I thought that DisplayMmember path would work when its set from item source
<ComboBox x:Name="cboCitys" ItemsSource="{Binding}" DisplayMemberPath="{Binding timing_title}" HorizontalAlignment="Left" Margin="18,73,0,0" VerticalAlignment="Top" Width="343" Height="51">
</ComboBox>
How i Fetech the data
popcornpk_Dal _dal = new popcornpk_Dal();
CitysList _mycities = await _dal.GetCityListAsync();
cboCitys.ItemsSource = _mycities.cityList;
DisplayMemberPath is used to specify the path to the displayed property, you don't need to bind it
DisplayMemberPath="timing_title"
beside that it would be much more elegant if you bind your combobox's itemSource to a Collection property, and implement the INotifyPropertyChanged in your CitysList class, like so :
public class CitysList:INotifyPropertyChanged
{
private ObservableCollection<City> _citylist ;
public ObservableCollection<City> CityList
{
get
{
return _citylist;
}
set
{
if (_citylist == value)
{
return;
}
_citylist = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Xaml
<ComboBox ItemsSource="{Binding CitysList}" DisplayMemberPath="timing_title" />
and don't forget to set the DataContext to an instance of the class that hold the collection, and to update the List just reinstantiate it
CityList = new ObservableCollection<City>(await _dal.GetCityListAsync());
Update
To set the dataContext,
First Create a CityList property in the codebehind,
private CitysList _cityList ;
public CitysList CityList
{
get
{
return _cityList;
}
set
{
if (_cityList == value)
{
return;
}
_cityList = value;
OnPropertyChanged();
}
}
Second, set the page DataContext to the codebehind using
this.DataContext=this; //in the main constructor
or from Xaml using
DataContext="{Binding RelativeSource={RelativeSource Self}}"
the Combobox will automatically inherit the page DataContext
Third Bind to your collection
<ComboBox x:Name="cboCitys" ItemsSource="{Binding CityList.CityList}" DisplayMemberPath="timing_title" HorizontalAlignment="Left" Margin="18,73,0,0" VerticalAlignment="Top" Width="343" Height="51">
PS: you may as well consider adding the CityList collection directly in your codebehind there are no need to add a class just to hold that collection !

Using commands in the Data Model scope

In my program I am trying to write commands for a User Control that will toggle the isEnabled and isChecked property of a few controls. Attached to my User Control is a View Model and a Data Model. My commands and properties are in my Data Model (First of all, is this correct implementation?), and there is a property for my Data Model inside my View Model.
The commands are not working. I do not get any binding errors, and when I debug my code, the values are changed correctly. However, there is no visual feedback.
My View Model is set as the DataContext of the User Control in it's constructor.
My data is bound like this:
<CheckBox Command="{Binding Model.myCommand}" ... />
This is an example of what one of my commands looks like:
public Command myCommand { get { return _myCommand; } }
private void MyCommand_C()
{
if (_myCommand== true) //Checked
{
_checkBoxEnabled = true;
}
else //UnChecked
{
_checkBoxEnabled = false;
_checkBox = false;
}
}
Why aren't these commands functioning?
Commands should be implemented in the ViewModel.
There or in your Models, you should have Properties binded to the IsChecked and IsEnabled properties of your controls, and in the command, changing the properties will trigger PropertyChanged event which will update your views.
Example:
In your view :
<StackPanel>
<Button Command="{Binding ToggleCommand}"/>
<CheckBox IsChecked="{Binding Path=Model.IsChecked}"/>
<CheckBox IsEnabled="{Binding Path=Model.IsEnabled}"/>
</StackPanel>
ViewModel:
public class MainWindowViewModel : NotificationObject
{
public MainWindowViewModel()
{
Model = new MyModel();
ToggleCommand = new DelegateCommand(() =>
{
Model.IsChecked = !Model.IsChecked;
Model.IsEnabled = !Model.IsEnabled;
});
}
public DelegateCommand ToggleCommand { get; set; }
public MyModel Model { get; set; }
}
Model:
public class MyModel : INotifyPropertyChanged
{
private bool _isChecked;
private bool _isEnabled;
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
_isChecked = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
public bool IsEnabled
{
get
{
return _isEnabled;
}
set
{
_isEnabled = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsEnabled"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Hope this helps
First, your Command properties should be in your ViewModel, not your data model.
Setting that aside, you should not bind a CheckBox to a Command - commands are for elements that trigger actions (such as clicking a Button). A CheckBox should be bound to a bool property. Where the property should reside can be debated, but my opinion is that it should be in the ViewModel so you can keep the Property Changed Notification logic out of your data model.
A quick example:
public class MyViewModel : INotifyPropertyChanged
{
private bool _myCheckValue;
public bool MyCheckValue
{
get { return _myCheckValue; }
set
{
_myCheckValue = value;
this.RaisePropertyChanged("MyCheckValue");
}
}
//INotifyPropertyChange implementation not included...
}
And then in your XAML (assuming the ViewModel is the DataContext):
<CheckBox IsChecked="{Binding MyCheckValue}" ... />

Binding set to CheckBoxes in WPF

using Entity Framework (C#) I have a User class which has ONE:MANY mapping to the UserRight class (simply, user has a set of rights). Each right is identified by a string. And now, because the maximum number of possible rights is finite (<10) I'd like to have 10 CheckBoxes and edit the subset of rights for a given user manually.
What is the nice way to do it?
James
Create a RightViewModel class to contain user rights:
public class RightViewModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
Change("Name");
}
}
private bool _hasRight;
public bool HasRight
{
get { return _hasRight; }
set
{
_hasRight = value;
Change("HasRight");
}
}
public void Change(string strPropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(strPropertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Create a similar class for your user, containing a member Rightsof type ObservableCollection<RightViewModel>.
In you XAML, use an ItemsControl:
<ItemsControl ItemsSource="{Binding Rights}"
ItemTemplate="{StaticResource RightTemplate}"/>
And a template definition:
<DataTemplate x:Key="RightTemplate">
<CheckBox Content="{Binding Name}" IsChecked="{Binding HasRight, Mode=TwoWay}"/>
</DataTemplate>
Mode=TwoWay makes the binding update your RightViewModel instance.
Define the ItemsControl's ItemsPanel if you need to display your checkboxes with a different layout.
Finally set your user as the DataContext of your container.

Categories

Resources