I have a window with some controls where some controls open up other windows with different controls. The bindings in the first window work great, and what's weird is the bindings in one of the new windows work well also. There is a third window where bindings don't seem to be working.
When I built the second window, to my surprise, I hadn't explicitly set the data context of its xaml bindings to be the xaml.cs partial class where all of the bound-to properties exist. But the second window has bindings and those work just fine.
This is very weird to me, as I clearly don't know how the data context and bindings work. For the third window I can't even get the bindings to show up even for a simple string property.
Can someone point me to a thorough discussion on how data binding works? I think it's fairly straightforward what I want to do, set the datacontext for each window's xaml to be the xaml.cs partial class associated with it. For the second window it seems like that happened automatically, but the third doesn't even seem to work with a simple string property.
Here are the bindings from the second window.
<TextBlock Name = "ObjectLatLon" Text= "{Binding hoveredObjectLatLon}" Background='White' Width="150" Height="20" Canvas.Top="5" Canvas.Left="685"/>
<TextBlock Name = "CursorLatLon" Text= "{Binding cursorLatLon, UpdateSourceTrigger=PropertyChanged}" Background='White' Width="150" Height="20" Canvas.Top="5" Canvas.Left="840"/>
<TextBlock Name = "TimeTextbox" Text= "{Binding winTime}" Background='White' Width="150" Height="20" Canvas.Top="5" Canvas.Left="1005"/>
Here are the code behind properties.
public string hoveredObjectLatLon
{
get { return HoveredObjectLatLon; }
set { HoveredObjectLatLon = value; ObjectLatLon.Text = hoveredObjectLatLon; }
}
public void cursorLatLon(object sender, MouseEventArgs e)
{
Point p = Mouse.GetPosition(FlatStanley);
double[] d = CanvasCoords_to_LatLon(new double[2] { p.Y, p.X });
p = new Point(d[0], d[1]);
CursorLatLon.Text = "Lat: " + Math.Round(p.X, 2).ToString() + " Lon: " + Math.Round(p.Y, 2).ToString();
}
private void hovered2dorbiter(object sender, MouseEventArgs e)
{
Shape o = sender as Shape;
double[] d = CanvasCoords_to_LatLon(new double[] { Canvas.GetTop(o) + (o.Height/2), Canvas.GetLeft(o) + (o.Width/2) });
hoveredObjectLatLon = "Lat: " + Math.Round(d[0],2) + " Lon: " + Math.Round(d[1], 2);
}
I'd like to know how these bindings can work but the third window, which isn't binding to a simple string property, will not work even if I explicitly state something like 'datacontext = this.' I looked through the second window's code and no where do i ever address the datacontext, but of course if I don't explicitly say 'datacontext = this' for the first window, all of its bindings are at risk of not working.
Basically, binding is very murky for me, not necessarily the mechanics of how a binding is supposed to work, but moreso the management of different datacontexts.
At the moment the thing I'm thinking is my only option is to make a class for every bit of data that will be bound to by any and all of the windows I'm incorporating bindings into and just go all out with putting everything in there. From what I've read, you can only have one data context across your solution. Is that the case? Or is there a simple enough functionality that I can work with to just simply have the datacontext of a MyWPFWindow.xaml window to be its MyWOFWindow.xaml.cs partial class counterpart.
I really can't get the datacontext to work with this third window at all.
Here is my XAML:
Window x:Class="COSTUMOBJECTSOLUTION.CUSTOMOBJECT_Window"
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:CUSTOMOBJECTSOLUTION"
mc:Ignorable="d"
Title="CUSTOMOBJECT_Window" Height="300" Width="300">
<TextBlock Grid.Row ="0" Grid.Column="0" Name="COtextblock" Text="{Binding CO_Name}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
In the code behind i say the following in the constructor:
DataContext = new CUSTOMOBJECT_ViewModel(customobject, mw);
And this is the viewmodel class I'm trying to get it to work for.
internal class CUSTOMOBJECT_ViewModel
{
CUSTOMOBJECT customobject;
MainWindow mW;
string CO_Name { get; set; }
public CUSTOMOBJECT_ViewModel(CUSTOMOBJECT custobj, MainWindow mw)
{
customobject = custobj;
mW = mw;
CO_Name = CUSTOMOBJECT.ObjectName;
}
}
But the objectname string is not ever showing up, it wasn't showing up when i tried it in the code-behind either.
Related
I am stuck badly in a situation and don't know how to solve the problem. The situation is I have two views and each having corresponding ViewModel classes. (Lets say View1 and View2 and corresponding VM1 and VM2 classes )
The situation is View1 should launch first and i read some input from user(some integer value) and then after that View2 has to launch. The problem is View2 has a UI which has to repeat number of times the user entered the integer value 1 step before in previous View1 launch inside a textbox. So you can see the UI of View2 depends upon the input taken at View1 launch.
So right at the time when the user entered the integer value the VM2 must be aware of that value so that VM1 will intialize the VM2 class to repeat that ListBox (which contains UI) that many times.
My approach to do this is like this (which is obviously not working that's why i am here to take guidance from experienced MVVM guys).
I go to View2.cs class and before and call View1 there in MainWindow() just after InitializeComponent(); function so taht View1 will be launched before View2 will be launched. And i got the value from View1 here as user enters in it and then i pass to that value to VM2 constuctor (like this:vm = new ViewModel(age);) so that my UI will be repeated, "age" variable times (please see the code below)
Lets say this my View2:
<Window x:Class="MVVMDialogWithReturnProperty.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vM="clr-namespace:MVVMDialogWithReturnProperty.ViewModels"
Title="MainWindow" Height="450" Width="850">
<Window.DataContext>
<vM:ViewModel>
</vM:ViewModel>
</Window.DataContext>
<Grid Name="ButtonsContainer" Width="700" Height="600">
<ListBox VerticalAlignment="Stretch" Grid.Row="1" ItemsSource="{Binding lb_GlobalList}" ScrollViewer.VerticalScrollBarVisibility="Auto" BorderBrush="Gray" Margin="0,30,0,0" Grid.RowSpan="2" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
//I have more UI here
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Save" Grid.RowSpan="2" Background="DarkGray" HorizontalAlignment="Center" VerticalAlignment="Top"
Grid.Row="2" Width="60px" Height="25px" Name="savebtn" Margin="40,5,0,0"
Click="savebtn_Click"/>
</Grid>
</Window>
In my View2.cs i try to do this so that i will get the integer value entered by View1
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MyDialog dialog = new MyDialog(); //This will launch another Window which contains two textbox and textblocks. "Title" and "Age"
dialog.ShowDialog();
if (dialog.RetVal != null)
{
string name = dialog.RetVal.Title;
int age = dialog.RetVal.Age;
ViewModel vm=DataContext as ViewModel;
vm = new ViewModel(age);// Here i get the integer variable "age" from View1 and i pass to the constructor of View2's VM2 (which is named as ViewModel in my case)
}
}
private void savebtn_Click(object sender, RoutedEventArgs e)
{
var viewModel = DataContext as ViewModel;
Pyth py = new Pyth(viewModel);
this.Close();
}
}
I am doing this thing vm = new ViewModel(age); because i think that it will call the VM2(ViewModel here) and will initialize my ListBox by number of "age"(integer value) times in its constructor beofre View2 Launches so that it will have UI repeating number of "Age" times by calling VM2's constructor like this :
public ViewModel(int numberOfRepeatitions)
{
_canExecute = true;
lb_GlobalList = new ObservableCollection<tablegenerateModel>();
for (int i = 1; i < numberOfRepeatitions; i++)
{
_lb_GlobalList.Add(lb_Global = new tablegenerateModel()
{
Name = "table" + i,
//contains other UI also like Textbox, Textblocks etc.
});
}
}
But what i found is the UI do not repeat that many times, It just repeats the number of time i initialized statically in parameterless ViewModel constructor but not this one "public ViewModel(int numberOfRepeatitions)".
How to change the code such that i will have repeat UI of View2 that many times the user had entered "age"(integer value) in previous View1 launch.
I have spent several days on this issue and can't seem to get it to work.
I have a user control that is saved out to a xaml file with the following code:
StringBuilder outstr = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
XamlDesignerSerializationManager dsm = new
XamlDesignerSerializationManager(XmlWriter.Create(outstr, settings));
dsm.XamlWriterMode = XamlWriterMode.Expression;
System.Windows.Markup.XamlWriter.Save(test1, dsm);
String saveCard = outstr.ToString();
File.WriteAllText("inputEnum.xaml", saveCard);
Xaml for the user control:
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding DescriptionWidth}" />
<ColumnDefinition Width="{Binding ValueWidth}" />
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="1" Background="White" FontSize="{Binding FontSizeValue}" Width="Auto"
Padding="10,0,5,0" ItemsSource="{Binding ComboItemsProperty}" SelectedIndex="{Binding EnumSelectedIndex}">
</ComboBox>
</Grid>
The ItemsSource in the combobox is what is giving me problems.
When I save an instance of this usercontrol out to a file, from my understanding, the {Binding ComboItemsProperty} is lost. So, in the constructor of my usercontrol I have:
public UserInputEnum()
{
InitializeComponent();
Binding bind = new Binding();
bind.Mode = BindingMode.TwoWay;
bind.Source = this;
bind.Path = new PropertyPath("ComboItemsProperty");
this.SetBinding(ComboBox.ItemsSourceProperty, bind);
}
Here is my property and the changed method:
EnumItemsCollection ComboItems = new EnumItemsCollection();
public EnumItemsCollection ComboItemsProperty
{
get { return ComboItems; }
set
{
ComboItems = value;
OnPropertyChanged("ComboItemsProperty");
}
}
public void OnPropertyChanged(string propertyName)
{
getEnumItems(this.ComboItemsProperty, this.EnumSelectedIndex, this.ID, this.SubmodeID);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this.ComboItems, new PropertyChangedEventArgs(propertyName));
}
}
Just a note. EnumItemsCollection is a simple class that inherits off ObservableCollection. There is nothing else to this class. (not sure if this makes a difference).
I think this should work but when when I load the XAML file through the XAMLReader, my combobox items won't update.
EDIT:
I ran a little test on an instance of user control that wasn't loaded from XAML but is in the MainWindow.xaml.
Everything works fine. When I add to the ComboItemsProperty, the combobox updates.
So, I took away the {Binding ComboItemsProperty} and tried to set the binding in the code as above changing 'this' to the instance of the user control. Didn't work. This tells me it is the binding code that is not functioning correctly.
I'm fairly certain is the bind.Source line that is the issue. When it is in a UserControl I am unsure of what to put there.
EDIT:
Code that loads usercontrol from file:
FileStream stream = File.Open("usercontrol.xaml", FileMode.Open, FileAccess.Read);
ComboBox cmb = System.Windows.Markup.XamlReader.Load(stream) as ComboBox;
It loads perfectly fine. The Binding just isn't working(ItemsSource={Binding ComboItemsProperty}) because Bindings aren't saved out.
I load it from a file because this program will have many User Interfaces in a sense. Each one will be loaded by a different person using the program.
You need to the set context of the instance containing your property ComboItemsProperty. So instead of 'this' u should set it to this.DataContext or other class object instance containing the ItemSource property you have defined..
Try this,
Binding bind = new Binding();
bind.Mode = BindingMode.TwoWay;
bind.Source = this.DataContext;
bind.Path = new PropertyPath("ComboItemsProperty");
this.SetBinding(ComboBox.ItemsSourceProperty, bind);
Update
According to Serialization Limitations of XamlWriter.Save available on msdn,
Many design-time properties of the original XAML file may already be optimized or lost by the time that the XAML is loaded as in-memory objects, and are not preserved when you call Save to serialize.
Common references to objects made by various markup extension formats, such as StaticResource or Binding, will be dereferenced by the serialization process.
Conclusion, that I made out now is you cannot directly load the UserControl as whole by Serialization - Deserialization procedure of XAML. I think you can load the object instances by Serialization - Deserialization procedure on the DataContext of the UserControl i.e. the custom list(s) or object(s) you have databound.
For some reason I can't get my textbox/property binding to work "automatically" with Reactive UI.
My DataContext is setup like this:
My ViewModel is setup in the code behind for the MainWindow.xaml.cs:
public MainWindowViewModel ViewModel { get; protected set; }
public MainWindow()
{
ViewModel = new MainWindowViewModel();
}
In my WPF MainWindow.xaml I have the DataContext and the have a textbox element like this:
(Other properties ommitted)
<Window x:Name="Window">
<Grid DataContext="{Binding ViewModel, ElementName=Window}">
<StackPanel x:Name="ContentGrid" Grid.Row="1">
<TextBox Name="Password" Text="{Binding Password, Mode=TwoWay}" />
...
In the MainWindowViewModel I have a property and field like this:
string _PasswordConfirmation;
public string PasswordConfirmation
{
get { return _PasswordConfirmation; }
set { this.RaiseAndSetIfChanged(x => x.PasswordConfirmation, value); }
}
I also have a command that is setup like this:
var canHitOk = this.WhenAny(
x => x.Password,
x => x.PasswordConfirmation,
(pass, confirm) => (pass.Value == confirm.Value && pass.Value.Length > 3));
OkCommand = new ReactiveCommand(canHitOk);
As you can guess, I also have the password confirmation textbox/property setup too, and the command set for a button (The password confirmation TextBox has the same problem). Since the field/property combos are never updated in the ViewModel, the button is never enabled.
I debugged and confirmed that the ViewModel is bound to the XAML, but the text never gets updated in the field/property combos when typing in the TextBoxes.
I looked at the sample WP7 application in the GitHub repo and they manually bind the KeyUp event to set the text in the text in the property/field. If I follow this convention, I have this in my MainWindow.xaml:
Password.KeyUp += (o, e) =>
{
ViewModel.Password = Password.Text;
};
But is that the right way to do it? I thought Reactive UI would handle the binding as I've seen in other MVVM frameworks, unless I'm mistaken. Is there an easier way?
UPDATE
As Paul Betts pointed out in the comments in his answer, you can enable the binding to automatically update by adding this property to the binding:
UpdateSourceTrigger=PropertyChanged
which would make the TextBox element look like this:
<TextBox Name="Password" Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" />
The problem is that WP7 TextBox doesn't Update its binding automatically on KeyUp - this is a problem with every MVVM framework sadly.
To learn WP7 i'm making a simple soundboard application.
This is my code.
Please ask if i accidentally left something out in my tries to keep things simple.
MainViewModel contains this collection
public ObservableCollection<SoundViewModel> Sounds { get; private set; }
The soundViewModel contains these properties, they are both notifying any property changes
public string FileName
public string Name
Xaml / View contains a listbox bound to the Sounds collection on the viewmodel
<ListBox x:Name="FirstListBox" Margin="0,0,-12,0" ItemsSource="{Binding Sounds}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="Play" CommandParameter="{Binding FileName}" Command="{Binding DataContext.PlaySound, ElementName=FirstListBox}" />
<es:Arc x:Name="arc" ..bla bla attributes. />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When i click the button, the parameter / filename is used to located the file and invoke the following method.
This method is enclosed in a RelayCommand and bound to the button in the datatemplate
private void PlaySound( string filename )
{
Stream stream = TitleContainer.OpenStream(filename + ".wav");
SoundEffect effect = SoundEffect.FromStream(stream);
PlayAnimation message = new PlayAnimation(effect.Duration);
Messenger.Default.Send<PlayAnimation>(message);
FrameworkDispatcher.Update();
effect.Play();
}
This works great.
Now i want a small animation playing while the sound is playing.
I'm thinking that i will just pass the length of the soundclip to the animation and then start it.
The messenger class sends a message (in the PlaySound method, above), and this code wires it up to a method
------ View / Xaml Constructor ------
Messenger.Default.Register<PlayAnimation>(this, ( action ) => PlayAnimation(action));
------ method PlayAnimation below ------
private void PlayAnimation(PlayAnimation parameter)
{
//Magic code starting the animation in the datatemplate of the listbox..
}
But i'm not exactly sure how i can start the animation.
The storyboard is a resource to the listbox, and the targetElement is the Arc element.
So the animation is simply the arc element's startAngle, and is just there to illustrate that a sound i currently playing, and how long the sound duration is.
Somehow i need to get a hold of the storyboard, but since the targetElement is inside a data-template, how will i know how to play the correct animation. That is if i'm even able to get reference to the storyboard?
Thanks in advance!
Please ask if there is anything
First off define your animation in your data template.
Next instead of binding the CommandParameter to "FileName" bind it to the object in your data template where the animation is defined. Normally your data template will contain a root layout panel where the animation is defined as a resource. Make sure that has an x:Name and bind your CommandParameter to that. So if your root layout container were called "grid" bind the CommandParameter to it like this.
CommandParameter="{Binding ElementName=grid, Mode=OneWay}"
Now in the handler for your relay command change the parameter to reflect that you are now passing in a FrameworkElement rather than your file name string. Also change your code to extract the Storyboard and use the DataContext to get back to your viewmodel to get the filename of the sound.
private void PlaySound( FrameworkElement obj )
{
var animation = obj.Resources["MyAnimation"] as Storyboard;
animation.Begin();
var selected = obj.DataContext as SoundViewModel;
var filename = selected.FileName;
Stream stream = TitleContainer.OpenStream(filename + ".wav");
SoundEffect effect = SoundEffect.FromStream(stream);
PlayAnimation message = new PlayAnimation(effect.Duration);
Messenger.Default.Send<PlayAnimation>(message);
FrameworkDispatcher.Update();
effect.Play();
}
UPDATE II
Problem was solved. Thank you.
For a simple Silverlight printing preview engine, my XAML looks like this (excerpt):
<Grid>
<TextBlock Text="{Binding IntroText}" />
<ItemsControl ItemsSource="{Binding DataItems}"
x:Name="DataItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
TextWrapping="Wrap"
Margin="0,2" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="{Binding OutroText}" />
</Grid>
I want to ensure that everything fits on a page, therefore i have a simple method:
public bool FitsOnPrintPage(Size pageDimensions)
{
Measure(new Size(pageDimensions.Width, Double.PositiveInfinity));
return
DesiredSize.Height <= pageDimensions.Height &&
DesiredSize.Width <= pageDimensions.Width;
}
Now we have a strange problem here which I can't explain:
The bound collection DataItems is a generic object List. When containing simple strings, the Measure(...) method works as expected and returns a properly calculated DesiredSize. So far, everything is working.
However, when having a simple object like this...
public class DataItem
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
...and changing the TextBlock Binding to <TextBlock Text="{Binding Path=Value1}"... the resulting view is identical, however the Measure(...) method doesn't return the expected values, the height of the Items is always zero. Also not working: keep Text Binding and override DataItems ToString() method. View working, Measure doesn't.
I was then trying to force a recalculation using methods like InvalidateMeasure() or UpdateLayout() on the DataTemplate or the whole page, without success.
Can you explain this?
UPDATE
Interesting: I've attached a simple custom ValueConverter to the TextBlock's Binding just for debugging reasons. When a string object is bound, I can see that Measure(...) is triggering the Binding - it's resolved first (i can see the debugger stepping into the ValueConverter) and measured afterwards. But when binding a custom class as described above, Measure(...) doesn't touch the Binding, i am stepping into the ValueConverters breakpoint "later". (Have to find out, when exactly)
Does this help you in any kind?
The answer is simple.. you working not in the 'silverlight way'
In Silverligth - it dosen't mather if string fits to the screen width or not, if string dosen't fit, just set TextBlock.Wrap to Wrap...
You have problem with this becose of 'old way of thinking'...
But if you want it so much try this:
var ContainerGrid = new Grid(); // create grid at runtime
// !!! it's important for controlToMesure.Parent property to be NULL, if it's not
// !!! then temporary remove controlToMesure from parent container...
ContainerGrid.Children.Add(controlToMesure); // add control that you want to mesure
ContainerGrid.Measure(new Size(pageWidth, pageHeight));
ContainerGrid.Arrange(new Rect(0, 0, pageWidth, pageHeight));
ContainerGrid.UpdateLayout();
var size = ((FrameworkElement)ContainerGrid.Children[0]).DesiredSize;
Here is the code from http://silverpdf.codeplex.com/
Maybee it would help you, but you have to modify it, to make it usable.
private System.Windows.Size CalculeteSize()
{
var s = new System.Windows.Controls.StackPanel()
{
VerticalAlignment = System.Windows.VerticalAlignment.Center,
HorizontalAlignment = System.Windows.HorizontalAlignment.Center
};
var fs = FontPool.GetFontStream(Typeface.FontFamily.Source);
s.Children.Add(new System.Windows.Controls.TextBlock
{
Text = Text,
FontSource = new FontSource(fs),
FontSize = EmSize,
FontFamily = Typeface.FontFamily,
FontStretch = Typeface.FontStretch,
FontStyle = Typeface.FontStyle,
FontWeight = Typeface.FontWeight,
});
s.Measure(new System.Windows.Size(double.MaxValue, double.MaxValue));
var aw = s.DesiredSize.Width;
var ah = s.DesiredSize.Height;
var size = new System.Windows.Size(aw, ah);
return size;
}
Solved
the problem was that the page controls were created and calculated first, and added to the displaying control after generation, because i wanted to avoid frequent UI updates. Something similar was even suggested by Ai_boy, who was trying to solve the problem by using an independent Grid Control - unfortunately this turned out as a misleading approach. Only after the generated page control was added to the visual tree, it automatically resolves the Bindings resulting in a proper size measuring.
Hope this helps anyone.