MVVM Light RaisePropertyChanged - c#

I am not able to get the UI to respond to RaisePropertyChanged for some reason. Well stuck.
CurrentClient is the part not firing to the UI. Really strange.
Hopefully someone can help me out. Thanks Scott
public class ClientViewModel : ViewModelBase
{
public RelayCommand<ClientDetail> SelectedClient { get; private set; }
public ICollectionView ClientView { get; private set; }
private readonly IClients _clientsService;
public ClientViewModel(IClients clientsService)
{
_clientsService = clientsService;
SelectedClient = new RelayCommand<ClientDetail>(InSelectedClient);
ClientDetailsList = new ObservableCollection<ClientDetail>(_clientsService.LoadClientList());
//CurrentClient = ClientDetailsList.ToList().FirstOrDefault();
ClientView = CollectionViewSource.GetDefaultView(ClientDetailsList);
}
private void InSelectedClient(ClientDetail obj)
{
CurrentClient = obj as ClientDetail;
}
private ObservableCollection<ClientDetail> _clientDetailsList;
public ObservableCollection<ClientDetail> ClientDetailsList
{
get { return _clientDetailsList; }
set { _clientDetailsList = value;
RaisePropertyChanged("ClientDetailsList"); }
}
private ClientDetail _currentClient;
public ClientDetail CurrentClient
{
get { return _currentClient; }
set
{
if (_currentClient == value)
{ return; }
_currentClient = value;
RaisePropertyChanged("CurrentClient");
}
}
}
My XAML :
<ListBox Name="lbClientList"
ItemsSource="{Binding ClientView}" ItemTemplate="{DynamicResource DTClientList}"
Background="{x:Null}" BorderThickness="0,0,1,0" BorderBrush="#FF434343" >
<ListBox.Resources>
<DataTemplate x:Key="DTClientList">
<StackPanel Orientation="Horizontal">
<TextBlock TextWrapping="Wrap" Text="{Binding CM_CompanyID}" Margin="0,0,5,0" Width="25" Foreground="#FF3590FC"/>
<TextBlock TextWrapping="Wrap" Text="{Binding CM_CompanyName}" Width="150" Foreground="#FF3590FC"/>
<TextBlock TextWrapping="Wrap" Text="{Binding CM_MainContact}" Width="100" Foreground="#FF3590FC"/>
</StackPanel>
</DataTemplate>
</ListBox.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<Command:EventToCommand Command="{Binding SelectedClient, Mode=OneWay}" CommandParameter="{Binding SelectedItem, ElementName=lbClientList}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.DataContext>
<Binding Path="ClientView" Source="{StaticResource ServiceLocator}" UpdateSourceTrigger="PropertyChanged"/>
</ListBox.DataContext>
</ListBox>
In debug the code is hitting RaiseProertyChange but I am seeing nothing on my UI
<TextBox Text="{Binding CurrentClient.CM_Address1}" TextWrapping="Wrap" VerticalAlignment="Center" Width="210" Background="{DynamicResource MainBackgrouund}" BorderThickness="0,0,0,1" >

Ok not really sure about your code structure since I cannot reproduce your issue with the current info.
However couple things that might be worth you knowing about,
In InSelectedClient(...) the argument is already of type ClientDetail making the as cast redundant inside the function.
Next why are you even using a EventToCommand for this? you hold the ListBox selected item via the EventToCommand in CurrentClient. Rather just bind it directly.
something like:
<ListBox ...
SelectedItem="{Binding CurrentClient}">
...
Finally If you don't have any specific logic in the VM relating to CurrentClient and if there is really no necessity holding on to it, then you can get rid of it by binding your TextBox directly to the ListBox.
something like
<TextBox Text="{Binding ElementName=lbClientList, Path=SelectedItem.CM_Address1}" />
I'm guessing CM_Address1 is a "property" in ClientDetail class
Now all these approaches work fine for me. I'd suggest putting together a reproducible stand-alone example if none of these work for you. I can attach a sample of these methods in a demo if you want(not really sure it's gonna be of much help if your code is structured well different)

Related

Adding buttons to WPF from ObservableCollection using MVVM pattern

I'm trying to add buttons to UserControl. I have to follow MVVM pattern. I have created a class, DeviceButton, with different set/gets and a constructor. In a viewmodel, used as datacontext, is the ObservableCollection and a get-method for the collection. I have bound the collection to a ItemsControl source and tried to add a template. I guess I'm missing something 'cause the buttons won't load.
The UserControl is added into a tab (using dragablz) which as well is a part of a ObservableCollection, also added at run time (this is working just fine). The idea is, that the tab has a list of buttons that has to be created at run time, where the list is fetched from a web service - so the buttons has to be added dynamically/programatically. Overview is just the first tab - a template for each tab (reflecting the fetched items) is being implemented when the buttons work. For now, I'm just adding a test button to the collection, but as stated, this won't show. What am I missing?
I have a Overview.xaml file:
<UserControl // attributes omitted to save spaces... ask if needed>
<StackPanel>
<Border>
<TextBlock FontSize="16" FontWeight="Bold" TextWrapping="WrapWithOverflow"
TextAlignment="Center" HorizontalAlignment="Center"
Foreground="{DynamicResource AccentColorBrush}">
Welcome to Greenhouse App
</TextBlock>
</Border>
<ItemsControl ItemsSource="{Binding DeviceButtons}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vmodel:DeviceButton}">
<Button Width="50" Height="50" Margin="10 10 0 0" Command="{Binding OpenTab}"
CommandParameter="{Binding DeviceType}" HorizontalAlignment="Left"
VerticalAlignment="Top" Style="{DynamicResource SquareButtonStyle}">
<StackPanel>
<Image Source="{Binding ImageUrl}" />
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
// Manually added test button...
<Button x:Name="windMill" HorizontalAlignment="Left" Margin="10,0,0,0"
VerticalAlignment="Top" Width="50" Height="50"
Command="{Binding OpenTab}" FontFamily="Segoe Ui"
Style="{DynamicResource SquareButtonStyle}">
<StackPanel>
<Image Source="/Greenhouse;component/Icons/Windmill.png" />
</StackPanel>
</Button
</StackPanel>
I'm trying to add an ObservableCollection of type DeviceButton, the _deviceButtons collection (Binded as ItemsSource to the ItemsControl)
The tabList items are working just find, and I can manually add more if needed (these will later be added through the OpenNewTab-command, which should be bound to the buttons)
The DeviceButton file:
public class DeviceButton
{
private readonly string _content;
private readonly string _deviceType;
private readonly string _imageUrl;
public DeviceButton(string content, string deviceType, string imageUrl)
{
_content = content;
_deviceType = deviceType;
_imageUrl = imageUrl;
}
public string Content
{
get { return _content; }
}
public string DeviceType
{
get { return _deviceType; }
}
public string ImageUrl
{
get { return _imageUrl; }
}
}
The collection is located in a viewmodel file, MainWindowViewModel.cs:
public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged
{
public ICommand OpenTab { get; set; }
private string tabControl { get; set; } = "0";
private IInterTabClient _interTabClient;
private ObservableCollection<TabContent> _tabContents = new ObservableCollection<TabContent>();
private ObservableCollection<DeviceButton> _deviceButtons = new ObservableCollection<DeviceButton>();
public MainWindowViewModel()
{
_interTabClient = new MyInterTabClient();
DeviceButtons.Add(new DeviceButton("Windturbine", "windturbine", "/Greenhouse;component/Icons/Windmill.png"));
TabContents.Add(new TabContent("Overview", new Overview()));
OpenTab = new RelayCommand<object>(OpenNewTab);
}
private void OpenNewTab(object obj)
{
MessageBox.Show("Yo"); // Not yet implemented
RaisePropertyChanged(() => tabControl);
}
public ObservableCollection<TabContent> TabContents
{
get { return _tabContents; }
}
public ObservableCollection<DeviceButton> DeviceButtons
{
get { return _deviceButtons; }
}
public IInterTabClient InterTabClient
{
get { return _interTabClient; }
}
}
The buttons aren't loaded when I start the program (only the "test" button added manually in the WPF).
The UserControl, Overview, is considered a tab in another Controls.Metrowindow, MainWindow.xaml:
<Window.Resources>
<Style TargetType="{x:Type dragablz:TabablzControl}">
<Setter Property="CustomHeaderItemTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type viewmodel:TabContent}">
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type viewmodel:TabContent}">
<ContentPresenter Margin="4" Content="{Binding Content}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<dragablz:TabablzControl SelectedIndex="{Binding tabControl}" ItemsSource="{Binding TabContents}" x:Name="InitialTabablzControl" Margin="4 0 4 4">
<dragablz:TabablzControl.InterTabController>
<dragablz:InterTabController InterTabClient="{Binding MyInterTabClient}" />
</dragablz:TabablzControl.InterTabController>
</dragablz:TabablzControl>
I guess it's an issue with the resources/binding in the Overview.xaml, but I've exhausted all suggested solutions I could find.
Issue was in binding
ItemsSource="{Binding Path=DataContext.TabContents,
RelativeSource={RelativeSource AncestorLevel=1,
AncestorType={x:Type Window}, Mode=FindAncestor}}"
I suspect the DataContext is being set to a TabContent object, which does not have a DeviceButtons property
I would suggest using a tool like Snoop to verify your DataContext is what you expect.

WPF ItemsControl binding issue

I have problem with Binding: see my code
This is Xaml code:
<ItemsControl x:Name="lbOpenInvoices" ItemsSource="{Binding Path=ocOpenInvoices}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" VerticalAlignment="Top" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="btnOpenInvoice" Click="btnOpenInvoice_Click" Style="{StaticResource OpenInvoicesButton}">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Converter={StaticResource InvoiceNoTableNo}}"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="{Binding Converter={StaticResource InvoiceNoInvoiceId}}"/>
<TextBlock Text="{Binding TotalAmount}" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
<TextBlock Text="{Binding Converter={StaticResource InvoiceDateTime}}"/>
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
In Code behind I have declared the ocOpenInvoices ObservableCollection:
public ObservableCollection<Invoice> ocOpenInvoices { get; set; }
And In my Window Loadded event:
void SaleWindow_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = this;
}
But It driving me Crazy because the ItemControl does not respond to ocOpenInvoices ObservableCollection.
When I give it the ItemsSource from codebehind it works :(, I have tried to give it the ElementName but it still not respond.
Please can you help and tell me what's my problem? what I miss here?
Thanks in advance.
Try abstracting your observable collection over a private variable a private, it will work.
Replace
public ObservableCollection<Invoice> ocOpenInvoices { get; set; }
with
private ObservableCollection<Invoice> _ocOpenInvoices;
public ObservableCollection<Invoice> ocOpenInvoices
{
get { return _ocOpenInvoices ; }
set { _ocOpenInvoices = value; OnPropertyChange("ocOpenInvoices"); }
}
Please ignore this OnPropertyChange, if you have already implemented INotifyPropertyChanged in your own way, otherwise, it would be INotifyPropertyChanged that can solve your issue.
Make sure you initialize your ObservableCollection within Window's constructor or Window loaded event.
void SaleWindow_Loaded(object sender, RoutedEventArgs e)
{
ocOpenInvoices = new ObservableCollection<Invoice>();
this.DataContext = this;
}
If you are initializing it somewhere else than make sure you implement INotifyPropertyChanged and raise PropertyChanged.

Two-way Binding error in longlistselector, with own datatemplate and class

Hey Embarrassingly I have not been able to get two-way binding working from the examples I have found. Here is one of my dataTemplates.
<local:ChatRoomTemplateSelector.YourSelf>
<DataTemplate x:Name="YourSelf">
<StackPanel>
<Grid x:Name="YourSelfGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<UserControl Grid.Row="0">
<Path Data="" Fill="LawnGreen" StrokeThickness="0" Stretch="Fill" UseLayoutRounding="True"/>
</UserControl>
<TextBox Text="{Binding Path=Post}" IsReadOnly="{Binding readOnly}" FontSize="20" Margin="39,10" TextWrapping="Wrap" TextAlignment="Center"/>
</Grid>
<StackPanel Orientation="Horizontal">
<Path Data="" Fill="LawnGreen" StrokeThickness="0" Stretch="Fill" UseLayoutRounding="True" Margin="50,-1,0,0"/>
<TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="25" TextWrapping="Wrap"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</local:ChatRoomTemplateSelector.YourSelf>
The element I need to have two-way binding on is Text="{Binding Path=Post}". But cannot get it working. The longlistselector I have, has the itemsource set to:
System.Collections.ObjectModel.ObservableCollection<Data> source = new System.Collections.ObjectModel.ObservableCollection<Data>();
This source is then set to my Longlistselectors itemsource.
Where the Data class is:
public class Data : INotifyPropertyChanged
{
public string Name
{
get;
set;
}
private string _dl;
public string Post
{
get { return _dl; }
set
{
if (value != _dl)
{
_dl = value;
NotifyPropertyChanged("Post");
}
}
}
public string User
{
get;
set;
}
public bool readOnly
{
get;
set;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
This is What I have tried with no success. Hope you can help me with the tweak that gives me two-way binding for the Post, in Data.
Extra Info
I now tried the mode=TwoWay, and finally got that working. Code updated.
But now the update is only happening upon lostfocus. But I need the update of the variable to happen when text is inserted.
So I need to set UpdateSourceTrigger.
But what should this be set to?
Solution
<TextBox TextChanged="OnTextBoxTextChanged"
Text="{Binding MyText, Mode=TwoWay,
UpdateSourceTrigger=Explicit}" />
UpdateSourceTrigger = Explicit is a smart bonus here. What is it? Explicit: Updates the binding source only when you call the UpdateSource method. It saves you one extra binding set when the user leaves the TextBox.
In C#:
private void OnTextBoxTextChanged( object sender, TextChangedEventArgs e )
{
TextBox textBox = sender as TextBox;
// Update the binding source
BindingExpression bindingExpr = textBox.GetBindingExpression( TextBox.TextProperty );
bindingExpr.UpdateSource();
}:
If you want your TextBox to be two way binded, then you should use Text="{Binding Path=Post,Mode=TwoWay}"
Windows phone don't support UpdateSourceTrigger=PropertyChanged but you can find a way to do that here .
Or an alternative is to use the UpdateTextBindingOnPropertyChanged from the Prism library. You can just download the source code of the file I linked and set it in in xaml like this:
<TextBox ...>
<i:Interaction.Behaviors>
<prism:UpdateTextBindingOnPropertyChanged/>
</i:Interaction.Behaviors>
</TextBox>

WPF datatemplateselector not getting called

Hello I'm trying to dynamically change datatemplate but my method SelectTemplate in class TreeViewItemTemplateSelector never getting called (I've checked it by debugger) :( please help me :)
Code from xaml MainWindow:
Code in code behind:
Your problem seems to be that your TreeViewCustomItem is inheriting from TreeViewItem. (As seen in http://pastebin.com/jnP2nWMF)
Removing that inheritance (and the dependency property) causes the template selector to fire fine. What were/are you trying to achieve with the node item?
Looking at the OutputWindow, I get this message:
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='TreeViewCustomItem'
You don't have to have items inherit from the TreeViewItem in order to bind them to a TreeView, TreeViewItem is something that the TreeView uses to hold the data, and then the DataTemplate is used to present the data.
Move DataTemplates from TreeView.Resources to Window.Resources
<Window.Resources><DataTemplate x:Key="DefaultTemplate">
<TextBlock Text="{Binding Header}"></TextBlock>
</DataTemplate><DataTemplate x:Key="Regulation">
<TextBlock Text="{Binding Path=Header}" FontWeight="Bold"></TextBlock>
</DataTemplate>
<DataTemplate x:Key="Article">
<TextBlock Text="{Binding Path=Header}" Foreground="Green"></TextBlock>
</DataTemplate>
<local:TreeViewItemTemplateSelector x:Key="TemplateSelector" DefaultTemplate="{StaticResource DefaultTemplate}" ArticleTemplate="{StaticResource Article}" RegulationTemplate="{StaticResource Regulation}" />
and make change
<TreeView ItemTemplateSelector="{StaticResource TemplateSelector}" Height="409" HorizontalAlignment="Left" Margin="10,10,0,0" Name="treeView1" VerticalAlignment="Top" Width="277" ItemsSource="{Binding}"/>
Update code and we will see. I put similar code into VS and it works so we need to take a closer look. So i checked this and changed
public class TreeViewCustomItem
{
public string Header { get; set; }
}
and this
listmy = new ObservableCollection<TreeViewCustomItem> { new TreeViewCustomItem { Header = "xD" }, new TreeViewCustomItem { Header = "xxD" } };
//treeView1.ItemsSource = listmy;
this.DataContext = listmy;
public class selector : DataTemplateSelector
{
public DataTemplate RegulationTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
TreeViewCustomItem treeViewItem = item as TreeViewCustomItem;
if (treeViewItem.Header == "xD")
{
return RegulationTemplate;
}
else
{
return DefaultTemplate;
}
}
}
and in XAML looks like this
xmlns:local="clr-namespace:WpfApplication1.Views">
<Window.Resources>
<DataTemplate x:Key="DefaultTemplate">
<TextBlock Text="{Binding Header}"></TextBlock>
</DataTemplate>
<DataTemplate x:Key="Regulation">
<TextBlock Text="{Binding Path=Header}" FontWeight="Bold"></TextBlock>
</DataTemplate>
<local:selector x:Key="selector_" DefaultTemplate="{StaticResource DefaultTemplate}" RegulationTemplate="{StaticResource Regulation}"/>
</Window.Resources>
<Grid>
<TreeView Height="409" HorizontalAlignment="Left" Margin="10,10,0,0" Name="treeView1" VerticalAlignment="Top" Width="277"
ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource selector_}"/>
</Grid>
And it works so my presumption is that problem is inside TreeViewCustomItem.

WPF: What's the proper way to do nested Binding

I have 3 models: Machine, Part, and Component. On my ui, I have bound one listbox to a list of Machines and a second listbox to the selected Machine's list of Parts. Now when the user selects a part, I would like to bind a datagrid to the selected part's list of components. I tried setting the datagrid's itemssource to:
ItemsSource="{Binding ElementName=PartSelectList, Path=SelectedItem.Components}"
I also tried setting its datacontext and itemssource
DataContext={Binding ElementName=PartSelectList, Path=SelectedItem}
ItemsSource={Binding Components}
However the datagrid does not automatically update if changes are made to the part's list of components via methods such as
part.Bags.Add(new Component(..));
Anyone have suggestions on how I should approach this?
Update: I have a class MachineCollection which implements INotifyPropertyChanged. In my xaml.cs code I create my MachineCollection object and bind the MainGrid to it's Machines property:
Page1.xaml.cs:
private MachineCollection machines
public Page1()
{
machineCollection = new MachineCollection();
MainGrid.DataContext = machineCollection.Machines
actionThread = new Thread(InitLists);
actionThread.Start();
}
public void InitLists()
{
machineCollection.Machines.AddRange(dbCon.GetAllMachines());
}
Classes:
public class MachineCollection : INotifyPropertyChanged
{
public List<Machine> Machines
{
get { return machines; }
set { machines = value; NotifyPropertyChanged("Machines"); }
}
}
public class Machine : INotifyPropertyChanged
{
public List<Part> Parts
{
get { return parts; }
set { parts = value; NotifyPropertyChanged("Parts");
}
}
public class Part : INotifyPropertyChanged
{
public List<Component> Components
{
get { return components; }
set { components = value; NotifyPropertyChanged("Components");
}
}
public class Component : INotifyPropertyChanged
{
public int Length
{
get { return length; }
set { length = value; NotifyPropertyChanged("Length");
}
}
XAML:
<Page>
<Grid Name="MainGrid">
<ListBox x:Name="MachineSelectList" HorizontalAlignment="Left" VerticalAlignment="Bottom"
Height="114" Width="231"
Foreground="White" Background="#FF7A7A7A" FontSize="16" BorderBrush="#FFC13131"
ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="200">
<TextBlock Text="{Binding SerialNumber}" TextAlignment="Left" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<Button Name="DeleteMachine" Tag="{Binding SerialNumber}" HorizontalAlignment="Right" VerticalAlignment="Center" Height="20"
Background="{x:Null}" BorderBrush="{x:Null}" Foreground="{x:Null}" Focusable="False" Click="Btn_Click" >
<Image Source="..\Resources\Images\delete.png" Stretch="Uniform"/>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Name="PartSelectList" HorizontalAlignment="Left" VerticalAlignment="Bottom"
Height="164" Width="231"
Background="#FF7A7A7A" Foreground="White" FontSize="16" BorderBrush="#FFC13131"
ItemsSource="{Binding Path=Parts}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="200">
<TextBlock Text="{Binding PartNumber}" TextAlignment="Left" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<Button Name="DeletePart" Tag="{Binding PartNumber}" HorizontalAlignment="Right" VerticalAlignment="Center" Height="20"
Background="{x:Null}" BorderBrush="{x:Null}" Foreground="{x:Null}" Focusable="False" Click="Btn_Click" >
<Image Source="..\Resources\Images\delete.png" Stretch="Uniform"/>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Page>
One way to solve this is using ObservableCollection, which is made for situations like this.
public ObservableCollection<Part> Parts
{
get { return parts; }
set { parts = value; NotifyPropertyChanged("Parts");
}
Or you could implement the interface INotifyCollectionChanged manually
The problem is, basically, that the binding system does not know when you call Add. If you were reassigning the Parts property each time, then the system would just redraw everything, so it would reflect the changes too, but if ObservableCollection is not a problem, that's the prefered way

Categories