Two way binding not working in WPF - c#

I have a WPF application with several dialogs with lots of controls. I have a dropdown list that is not binding from the C# to the XAML. The other way around works fine.
Here is the C#:
public class AdditionalCostView : ViewBase, IEquatable<AdditionalCostView>
{
// ..
public OfficeReferenceDataView Category
{
get { return _category; }
set
{
this._category = value;
this.OnPropertyChanged("Category");
}
}
// ..
}
and here is the corresponding XAML:
<ComboBox Grid.Row="0" Grid.Column="1" Validation.Error="Validation_Error"
DisplayMemberPath="Value" SelectedValuePath="ID"
ItemsSource="{Binding AllCategories}">
<ComboBox.SelectedItem>
<Binding Path="Category" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.NotifyOnValidationError>
<sys:Boolean>true</sys:Boolean>
</Binding.NotifyOnValidationError>
<Binding.NotifyOnSourceUpdated>
<sys:Boolean>true</sys:Boolean>
</Binding.NotifyOnSourceUpdated>
<Binding.NotifyOnTargetUpdated>
<sys:Boolean>true</sys:Boolean>
</Binding.NotifyOnTargetUpdated>
<Binding.ValidationRules>
<validators:MandatoryValueValidationRule IsRequired="True" Message="Please select a category."></validators:MandatoryValueValidationRule>
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedItem>
</ComboBox>
The AllCategories binding is working fine, but the SelectedItem binding is not. ViewBase implements INotifyPropertyChanged.
When I change the value of Category in my view in C#, nothing happens to the dropdown.
Where am I going wrong?
UPDATE
As per comment request, here is the code where I set my value:
private void ctlBooking_PricesAndFees_AdditionalCostEditing(object sender, RoutedEventArgs e)
{
try
{
AdditionalCostEventArgs args = e as AdditionalCostEventArgs;
AdditionalCostView costToEdit = args.AdditionalCost;
AdditionalCostView tempCost = args.AdditionalCost.Clone() as AdditionalCostView;
// ..
}
// ..
}
I know what you're thinking, because I thought the same thing. The Clone() is not copying the values or raising the events, but it is. I wrote it longhand after the clone to explicitly set those values but that still didn't work.
M

You can try with this XAML (don't forget to declare your data):
<UserControl.DataContext> <!--<Window.DataContext>-->
<data:AdditionalCostView />
</UserControl.DataContext> <!--</Window.DataContext>-->
<ComboBox Grid.Row="0" Grid.Column="1"
DisplayMemberPath="Value" SelectedValuePath="ID"
ItemsSource="{Binding AllCategories, Mode=TwoWay}" SelectedValue="{Binding Category.ID, Mode=TwoWay" />
Apply your ValidationRules in your model not in your UI. Search about DataAnnotations :P

Related

XAML set an initial value for a textbox that can also be changed

I have a textbox in my XAML that shows the current working directory. The XAML code looks like this:
<StackPanel x:Name="StackCurrentPath" Orientation="Vertical" Margin="25,25,25,5">
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<TextBox materialDesign:HintAssist.Hint="Path of app*" materialDesign:HintAssist.HintOpacity="10" materialDesign:HintAssist.Background="White" materialDesign:ValidationAssist.Background="White" materialDesign:HintAssist.Foreground="#FF002655" Style="{StaticResource MaterialDesignOutlinedTextFieldTextBox}" x:Name="txtboxCurrentPath" Width="230" TextChanged="txtboxCurrentPath_TextChanged">
<Binding Path="CurrentPath" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" Source="{StaticResource MyConfigurations}" >
<Binding.ValidationRules>
<local:PathValidation ValidatesOnTargetUpdated="True"/>
</Binding.ValidationRules>
</Binding>
</TextBox>
</StackPanel>
</StackPanel>
The code behind looks like this:
public partial class PathSection: Page
{
private string currentDirectoryPath = System.AppDomain.CurrentDomain.BaseDirectory;
public PathSection()
{
var currentDir = System.IO.Path.GetFullPath(System.IO.Path.Combine(currentDirectoryPath, #"..\Assets\"));
}
InitializeComponent();
txtboxCurrentPath.Text = currentDir;
private void txtboxCurrentPath_TextChanged(object sender, RoutedEventArgs e)
{
//some code here for binding the value to MyConfigurations.CurrentPath
}
}
I am trying to make it so that initially, the textbox should display the path on its own but the user can still make changes to it if they want. The problem I have is that when I do make changes and when I navigate back to this page, the components are reinitialized and the path that the user had changed gets overwritten. How can I make it so that the initial value is current path on its own however the user can still change and it won't get overwritten when they come back?
change
txtboxCurrentPath.Text = currentDir;
to
txtboxCurrentPath.Text = string.IsNullOrEmpty(MyConfigurations.CurrentPath) ? currentDir : MyConfigurations.CurrentPath;
but I think your design is not correct

WPF Draw primitives in ComboBox

I am making program for representation of network graph. I have here many attributes for connection between two nodes. Actually I am trying make a combobox with types of dashes like in MS Word. My code of ComboBox:
<ComboBox ItemsSource="{Binding Dashes}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Canvas >
<Line X1="0" Y1="0" X2="100" Y2="100" StrokeDashArray="{Binding Dash}"/>
</Canvas>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Code behind:
private DoubleCollection _dash;
private List<DoubleCollection> _listOfDashes;
public DoubleCollection Dash
{
get { return _dash; }
set
{
_dash = value;
OnPropertyChanged("Dash");
}
}
public List<DoubleCollection> Dashes
{
get { return _listOfDashes; }
set
{
_listOfDashes = value;
OnPropertyChanged("Dashes");
}
}
After launch the program the ComboBox isn't empty (there is two selectable object) but items are empty. Where I am making a mistake?
You are binding the ItemsSource property to your ViewModel Dashes property. It means that every ItemTemplate can be bound on each element of the Dashes collection (i.e. a DoubleCollection).
Try to change your XAML in this way:
<ComboBox ItemsSource="{Binding Dashes}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Line X1="0" Y1="0" X2="100" Y2="0" StrokeDashArray="{Binding}" Stroke="Black" Margin="6" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
It means that each Line has its StrokeDashArray binded to each DoubleCollection that belongs to Dashes collection. So in this way:
vm = new ViewModel();
vm.Dashes = new List<DoubleCollection>();
vm.Dashes.Add(new DoubleCollection());
vm.Dashes[0].Add(2);
vm.Dashes[0].Add(3);
vm.Dashes.Add(new DoubleCollection());
vm.Dashes[1].Add(1);
vm.Dashes[1].Add(4);
you will see two different lines in your ComboBox. I hope it can help you.

secondary listbox not show data

i've two listbox binded with properties of my viewmodel, the first listbox shows LstEmpresas and work fine, when I select a item, the property SelectedCompany sets fine, all ok.
In SelectedCompany's set of my viewmodel, I call a method than pupulate a secondary list (LtsEjercicios) and work fine too (LtsEjercicios populate perfectly depends that item i've selected in the first listbox).
The secondary listbox binds his ItemSource from LtsEjercicios object that, across viewmodel, is updated.
But the secondary listbox NOT SHOW any data, i'm crazy yet.
This viewModel code
public class frmEmpresasVM : ViewModelBase
{
//propiedades
EmpresasDataAccess empresasController = new EmpresasDataAccess();
private ObservableCollection<EmpresasTable> lstEmpresas;
private ObservableCollection<EmpresasEjerciciosTable> ltsEjercicios;
public ObservableCollection<EmpresasTable> LstEmpresas
{
get
{
return lstEmpresas;
}
set
{
lstEmpresas = value; RaisePropertyChanged("LstEmpresas");
}
}
public ObservableCollection<EmpresasEjerciciosTable> LtsEjercicios
{
get
{
return ltsEjercicios;
}
set
{
ltsEjercicios = value; RaisePropertyChanged("LtsEjercicios");
}
}
//selected company in listbox
private int selectedCompany;
public int SelectedCompany
{
get
{
return selectedCompany;
}
set
{
selectedCompany = value;
LtsEjercicios = empresasController.SelectExercicesById(selectedCompany.ToString());
RaisePropertyChanged("SelectedCompany");
}
}
//main constructor, default values for lists
public frmEmpresasVM()
{
LstEmpresas = empresasController.SelectOnlyNames();
LtsEjercicios = empresasController.SelectExercicesById("0");
}
and, XAML for the view
<ListBox x:Name="companyList" HorizontalAlignment="Left" Height="205" Margin="20,30,0,0" VerticalAlignment="Top" Width="450" ItemsSource="{Binding LstEmpresas, Mode=OneWay}" SelectedValue="{Binding SelectedCompany, Mode=TwoWay}" SelectedItem="{Binding LtsEjercicios, Mode=OneWay}" SelectedIndex="0" DisplayMemberPath="Nombre" SelectedValuePath="Id" IsSynchronizedWithCurrentItem="True" SelectionChanged="companyList_SelectionChanged_1">
<ListBox.ItemBindingGroup>
<BindingGroup/>
</ListBox.ItemBindingGroup>
<ListBox.DataContext>
<InnovaCommerceHosteleryViewModels:frmEmpresasVM/>
</ListBox.DataContext>
</ListBox>
<TextBlock x:Name="lblEjercicio" HorizontalAlignment="Left" Height="20" Margin="475,10,0,0" TextWrapping="Wrap" Text="Ejercicios" VerticalAlignment="Top" Width="95"/>
<ListBox x:Name="excercicesList" HorizontalAlignment="Left" Height="205" Margin="475,30,0,0" VerticalAlignment="Top" Width="110" ItemsSource="{Binding LtsEjercicios, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Ejercicio" SelectedValuePath="Id" SelectedIndex="0" IsSynchronizedWithCurrentItem="True">
<ListBox.DataContext>
<InnovaCommerceHosteleryViewModels:frmEmpresasVM/>
</ListBox.DataContext>
Al Data provides from a MySQL Database from both tables (empresas y empresas_ejercicios).
My goal is, when user select a item in listbox1 (empresas) show exercices in listbox2 (empresas_ejercicios).
If exist other path to determine this operation, i'm all eyes!!!
Thanks in advance.
Not sure if this is the only problem in your code, but what you're doing by setting
<ListBox.DataContext>
<InnovaCommerceHosteleryViewModels:frmEmpresasVM/>
</ListBox.DataContext>
for both list boxes is creating a separate instance of the ViewModel for each list box. Now I guess if you make any changes in the first list box' ViewModel instance, these changes will not be reflected in the second list box because this one binds to a totally different ViewModel instance.
Instead, try globally binding the ViewModel to the whole window / page / user control / etc. (depending on whether you;re doing WPF, Windows Phone, etc.), and let the list boxes inherit it to ensure that only one ViewModel instance is involved:
<Window.DataContext>
<InnovaCommerceHosteleryViewModels:frmEmpresasVM/>
</Window.DataContext>
EDIT:
Alternatively, you might as well instantiate the ViewModel once and store it as global resource:
<Window.Resources>
<InnovaCommerceHosteleryViewModels:frmEmpresasVM x:Key="Viewmodel" />
</Window.Resources>
and in the listboxes' DataContext just reference this global resouce:
<ListBox DataContext="{StaticResource Viewmodel}" ... />

Can I swap a buttons content databind to a different databind in code?

I cannot find any examples to make me understand how and if I can change the databind in c# at the click of a button on, in my case a toggleswitch, Basically I have 32 buttons in my app and those 32 buttons act the same but need different text with-in them depending on some toggle switches they are currently databinded so the text can be saved and retrieved from local storage but what values it gets depends on the state of these toggle switches.
So I currently have :
<Button x:Name="_ovButton1" Content="{Binding Source={StaticResource AppSettings}, Path=ovName1_1Value, Mode=TwoWay}" Margin="2,0,250,0" VerticalAlignment="Top" FontSize="14" Height="72" FontWeight="Bold" MouseLeftButtonUp="_ovButton1_MouseLeftButtonUp" MouseLeftButtonDown="_ovButton1_MouseLeftButtonDown" ClickMode="Hover" Hold="_ovButton1_Hold"/>
and I want when a user changes the state of a toggleswitch to change the
{StaticResource AppSettings}, Path=ovName1_1Value, Mode=TwoWay}
to for example:
{StaticResource AppSettings}, Path=ovName1_2Value, Mode=TwoWay}
but I cannot find any example that shows how to do that in c#
what code do I need to do that?
You can specify the target of databinding in code like this:
MyData myDataObject = new MyData(DateTime.Now);
Binding myBinding = new Binding("MyDataProperty");
myBinding.Source = myDataObject;
myText.SetBinding(TextBlock.TextProperty, myBinding);
See more at http://msdn.microsoft.com/en-us/library/ms742863.aspx
-- Edit Note I don't have access to a WP8 Emulator to test this ---
In the view model it looks like this:
public List<string> Members
{
get { return _Members; }
set { _Members = value; OnPropertyChanged(); }
}
public MainVM()
{
// Simulate Asychronous access, such as to a db.
Task.Run(() =>
{
Thread.Sleep(2000);
Members = new List<string>() {"Alpha", "Beta", "Gamma", "Omega"};
});
}
The code behind on the main page sets the datacontext (shared with all the child controls) as such:
public MainWindow()
{
InitializeComponent();
// Set the windows data context so all controls can have it.
DataContext = new MainVM();
}
The Mainpage Xaml to bind to members is like this
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[0] }" />
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[1] }" />
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[2] }" />
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[3] }" />
The result is this visually:
I based this on my blog article Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding for more info and a fuller example.
I think your best bet is going to be to use a collection of strings and bind to that collection. You can either change the collection when a toggle is switched, or keep 6 collections and bind to the collection that is for the toggle.
Xaml:
<ItemsControl x:Name="Buttons" ItemsSource="{Binding ButtonTextCollection}">
<ItemsControl.ItemsPanel>
<toolkit:WrapPanel/>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="100" Height="70" Content="{Binding}" Click="OnButtonClick"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Your code-behind would have the event handler for your button click
private void OnButtonClick(object sender, RoutedEventArgs e)
{
var text = ((Button) sender).Content.ToString();
// Send the text
}
Your ViewModel would hold the ButtonTextCollection property and would change based on the toggle.
public ICollection<string> ButtonTextCollection
{
get { return _buttonTextCollection; }
set
{
_buttonTextCollection = value;
OnPropertyChanged("ButtonTextCollection");
}
}
When you want to change the text, you would change the ButtonTextCollection
public void ChangeButtonText()
{
ButtonTextCollection = new Collection<string> {"A", "B",...};
}

Binding ComboBox ItemsSource in DataGrid RowDetailsTemplate

I am trying to bind an ItemsSource to a ComboBox in a RowDetailsTemplate. If I place a ComboBox outside the grid it works fine. I think this is occureing because of the ItemsSource property on the grid may be throwing off the ComboBox within the RowDetailsTemplate. XAML is below any thoughts?
Categories and CatTypes are two different ObservableCollections.
No error is occurring; the ComboBox just appears empty.
<ComboBox ItemsSource="{Binding CatTypes}"></ComboBox>
<my:DataGrid Name="gridProds" AutoGenerateColumns="False"
AlternatingRowBackground="Gainsboro" ItemsSource="{Binding Categories}">
<my:DataGrid.Columns>
<my:DataGridTextColumn x:Name="CatId" Header="CatID" Width="Auto" Binding="{Binding CategoryID}" />
<my:DataGridTextColumn Header="CatName" Width="Auto" Binding="{Binding CategoryName}" />
</my:DataGrid.Columns>
<my:DataGrid.RowDetailsTemplate>
<DataTemplate>
<Border>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label>ID:</Label>
<TextBox Name="txtGridCatId" Text="{Binding CategoryID}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label>Category Type:</Label>
<ComboBox ItemsSource="{Binding CatTypes}"></ComboBox>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</my:DataGrid.RowDetailsTemplate>
</my:DataGrid>
There is a class in the called DataSource in which the following is done:
private ObservableCollection<string> _cattypes = new ObservableCollection<string> { };
public ObservableCollection<string> CatTypes
{
get
{
_cattypes = new ObservableCollection<string> { };
SqlConnection con = new SqlConnection("MyConnStringHere;");
SqlCommand cmd = new SqlCommand("Select ID, CatType from PfCategoryType ORDER BY CatType", con);
con.Open();
SqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
string CatType = (string)rdr["CatType"];
_cattypes.Add(CatType);
}
con.Close();
return _cattypes;
}
}
In the MainWindow.xaml.cs I have:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataSource dataSource = new DataSource();
this.DataContext = dataSource;
}
}
If you checked the debug output in VS you would see the actual binding error. Most likely below code will fix it for you.
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=CatTypes}" />
If you can't get RelativeSource to work then use names. The property CatTypes is a property of some class which you created an object for and set as datacontext to some control. Just give that control a name (for example myControl) and bind like this:
<ComboBox ItemsSource="{Binding ElementName=myControl, Path=CatTypes}" />
If that don't work you need to post more of your code to figure out what you are doing wrong.
What happens if you try this?
<ComboBox DataContext="{Binding DataContext, ElementName=myControl}" ItemsSource="{Binding CatTypes}" />
(Of course you'd rename "myControl" to match the name of your window.)
Here, we're setting the data context of the combo box to be the same as the data context of the window. Since this is also the same data context of the first combo box in your XAML, I imagine the second combo box will start behaving like the first. (Although I worry that this will result in some unnecessary database connections, one per grid row.)
On second thought, if you need to set other properties in the context of the row, you won't want to set the data context of the entire ComboBox. In that case, I'd try something like this.
<ComboBox ItemsSource="{Binding ElementName=myControl, Path=DataContext.CatTypes}" SelectedItem="{Binding CategoryType}" />

Categories

Resources