Opening new a WPF Window - c#

I'm currently trying to have a pop-out button, such that when it's clicked the current grid will appear in a new window populated with the exact same information.
I got the new Window to appear but I'm trying to have the bindings set but unsure how to do that. If I can get some help please. When I execute OpenChildWindow it opens but nothing populates.
Viewmodel:
public ObservableCollection<PaymentInfo> AmortizationCollection {get; set;}
public void OpenChildWindow()
{
new ScheduleView().Show();
}
LoanView.xaml and ScheduleView.xaml
<telerik:RadGridView Grid.Row="3" Grid.ColumnSpan="3" x:Name="AmortGrid"
ScrollViewer.CanContentScroll ="True"
SelectedItem="{Binding SelectedRow, Mode=TwoWay}"
Height="650" AutoGenerateColumns="True" ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ShowGroupPanel="False"
ItemsSource="{Binding AmortizationCollection, Mode=TwoWay}">
My attempt at Content Setting
Scheduleview.xaml.cs
public ObservableCollection<PaymentInfo> AmortizationCollection { get; set; }
public ScheduleView()
{
InitializeComponent();
AmortGrid.ItemsSource = Content;
}
Viewmodel:
public void OpenChildWindow()
{
ScheduleView _newScheduleView = new ScheduleView();
_newScheduleView.Content = AmortizationCollection;
_newScheduleView.Show();
}
The window just appears (Collection) no datagrid or anyhting.

When using WPF, we tend to use DataTemplates to define what our data objects look like in the UI, so to recreate some UI control, we just need to pass the data object and ensure that the DataTemplate is accessible from both locations. You show code from your 'view model', but view models shouldn't know anything about the views and certainly don't open new windows like that.
However, incorrect terminology aside, your simplest solution would be to simply pass the collection to the new Window in the constructor:
public void OpenChildWindow()
{
ScheduleView _newScheduleView = new ScheduleView(AmortizationCollection);
_newScheduleView.Show();
}
Then in ScheduleView.xaml.cs:
public ScheduleView(ObservableCollection<PaymentInfo> amortizationCollection)
{
InitializeComponent();
AmortizationCollection = amortizationCollection;
AmortGrid.ItemsSource = AmortizationCollection;
}
As long as you have the same XAML in each place, it should look the same.

If that is a Window then you can try to set some Content to it, this way the new control that you'll pass will be set as the content of the new window. I think this property is missing in your code untill now.
// create instance
ScheduleView wind = new ScheduleView();
// set the content, can be a window or a page or anything
wind.Content = new SomeControl;
// show it.
wind.Show();
http://msdn.microsoft.com/en-us/library/system.windows.window.content(v=vs.95).aspx
A previous thread from Stack Overflow has this, but in a manner that was too general.
Changing content of Window (WPF)

Related

Update Multiple DataGrids in WPF for Header Title

I have a form that has a dynamic amount of datagrids that are brought in programmatically each one on a new tabpage.
My problem is that I need to change the Header of each column. I have tried doing it through a method
DataGridForSupplier.Columns[0].Header = "123";
but that keeps crashing with an error:
Index was out of range. Must be non-negative and less than the size of the collection
Turns out the problem is that the grid wasn't finished loading. So after waiting for all tabpage to load and add data to all the grids , even then the code
DataGridForSupplier.Columns[0].Header = "123";
would still crash. If the tabs are left to load on their own with no header tampering then the datagrid shows fine.
I would just LOVE to do this in XAML problem is that seeing that I don't know how many grids will load at run time I tried doing this at the back. So I'm open to any solution at this point. I tried finding a solution that would incorporate something that would 'theme' all the datagrids. Luckily all the datagrids headers will repeat across all tabs. So header 1 on tabpage 1 - 10 will be the same. Header 2 on tabpage 1 - 10 will be the same
Something like
<DataGridTemplateColumn.Header>
<TextBlock Text="{Binding DataContext.HeaderNameText, RelativeSource=>> RelativeSource AncestorType={x:Type DataGrid}}}" />
</DataGridTemplateColumn.Header>
but this needs to repeat for every Grid. This seems to escape me at the moment.
Any help would be welcome.
A rather lengthy answer, but this solution does not require any additional libraries, 3rd party tools, etc. You can expand it as you want later such as for adding hooks to mouse-move/over/drag/drop/focus, etc. First the premise on subclassing which I found out early in my learning WPF. You can not subclass a xaml file, but can by a .cs code file. In this case, I subclassed the DataGrid to MyDataGrid. Next, I created an interface for a known control type to ensure contact of given functions/methods/properties. I have stripped this version down to cover just what you need to get.
The interface below is just to expose any class using this interface MUST HAVE A METHOD called MyDataGridItemsChanged and expects a parameter of MyDataGrid.. easy enough
public interface IMyDataGridSource
{
void MyDataGridItemsChanged(MyDataGrid mdg);
}
Now, declaring in-code a MyDataGrid derived from DataGrid. In this class, I am adding a private property of type IMyDataGridSource to grab at run-time after datagrids are built and bound.
public class MyDataGrid : DataGrid
{
// place-holder to keep if so needed to expand later
IMyDataGridSource boundToObject;
public MyDataGrid()
{
// Force this class to trigger itself after the control is completely loaded,
// bound to whatever control and is ready to go
Loaded += MyDataGrid_Loaded;
}
private void MyDataGrid_Loaded(object sender, RoutedEventArgs e)
{
// when the datacontext binding is assigned or updated, see if it is based on
// the IMyDataGridSource object. If so, try to type-cast it and save into the private property
// in case you want to add other hooks to it directly, such as mouseClick, grid row changed, etc...
boundToObject = DataContext as IMyDataGridSource;
}
// OVERRIDE the DataGrid base class when items changed and the ItemsSource
// list/binding has been updated with a new set of records
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
// do whatever default behavior
base.OnItemsChanged(e);
// if the list is NOT bound to the data context of the IMyDataGridSource, get out
if (boundToObject == null)
return;
// the bound data context IS of expected type... call method to rebuild column headers
// since the "boundToObject" is known to be of IMyDataGridSource,
// we KNOW it has the method... Call it and pass this (MyDataGrid) to it
boundToObject.MyDataGridItemsChanged(this);
}
}
Next into your form where you put the data grid. You will need to add an "xmlns" reference to your project so you can add a "MyDataGrid" instead of just "DataGrid". In my case, my application is called "StackHelp" as this is where I do a variety of tests from other answers offered. The "xmlns:myApp" is just making an ALIAS "myApp" to the designer to it has access to the classes within my application. Then, I can add
<Window x:Class="StackHelp.MyMainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myApp="clr-namespace:StackHelp"
Title="Main Window" Height="700" Width="900">
<StackPanel>
<!-- adding button to the main window to show forced updated list only -->
<Button Content="Refresh Data" Width="100"
HorizontalAlignment="Left" Click="Button_Click" />
<myApp:MyDataGrid
ItemsSource="{Binding ItemsCollection, NotifyOnSourceUpdated=True}"
AutoGenerateColumns="True" />
</StackPanel>
</Window>
Now, into the MyMainWindow.cs code-behind
namespace StackHelp
{
public partial class MyMainWindow : Window
{
// you would have your own view model that all bindings really go to
MyViewModel VM;
public MyMainWindow()
{
// Create instance of the view model and set the window binding
// to this public object's DataContext
VM = new MyViewModel();
DataContext = VM;
// Now, draw the window and controls
InitializeComponent();
}
// for the form button, just to force a refresh of the data.
// you would obviously have your own method of querying data and refreshing.
// I am not obviously doing that, but you have your own way to do it.
private void Button_Click(object sender, RoutedEventArgs e)
{
// call my viewmodel object to refresh the data from whatever
// data origin .. sql, text, import, whatever
VM.Button_Refresh();
}
}
}
Finally to my sample ViewModel which incorporates the IMyDataGridSource
public class MyViewModel : IMyDataGridSource, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
public ObservableCollection<OneItem> ItemsCollection { get; set; }
= new ObservableCollection<OneItem>();
public void Button_Refresh()
{
ItemsCollection = new ObservableCollection<OneItem>
{
new OneItem{ DayName = "Sunday", DayOfWeek = 0},
new OneItem{ DayName = "Monday", DayOfWeek = 1},
new OneItem{ DayName = "Tuesday", DayOfWeek = 2},
new OneItem{ DayName = "Wednesday", DayOfWeek = 3},
new OneItem{ DayName = "Thursday", DayOfWeek = 4},
new OneItem{ DayName = "Friday", DayOfWeek = 5 },
new OneItem{ DayName = "Saturday", DayOfWeek = 6 }
};
RaisePropertyChanged("ItemsCollection");
}
// THIS is the magic hook exposed that will allow you to rebuild your
// grid column headers
public void MyDataGridItemsChanged(MyDataGrid mdg)
{
// if null or no column count, get out.
// column count will get set to zero if no previously set grid
// OR when the items grid is cleared out. don't crash if no columns
if (mdg == null)
return;
mdg.Columns[0].Header = "123";
}
}
Now, taking this a step further. I don't know how you manage your view models and you may have multiple grids in your forms and such. You could create the above MyViewModel class as a smaller subset such as MyDataGridManager class. So each datagrid is bound to its own MyDataGridManager instance. It has its own querying / populating list for the grid, handling its own rebuild column headers, mouse clicks (if you wanted to expand), record change selected, etc.
Hope this helps you some. Again, this does not require any other 3rd party libraries and you can extend as you need. I have personally done this and more to the data grid and several other controls for certain specific pattern handling.

How to bind the Image.Source in MVVM correctly?

I spent some time now trying to work this out but I am still stuck on it. I have a WPF application with a MVVM pattern. In my ViewModel I have three cases where:
X needs Y and Y is available
X needs Y and Y is not available
X doesn't need Y
I am trying to set an image icon on my view based on these conditions (Like a Check Mark, exclamation Mark... whatever).
In ViewModel:
I created my properties. On any GUI change, I set the variables based on the above cases similar to the following:
void MyBigFunctionToSetAllProperties()
{
// other cases
// ..
if (NeedsY && YExists)
{
// properties
StatusIconPath = "#\"/Resources/SessionView/X-With-Green-Check-White.png\"";
ResultIconPath = "#\"/Resources/SessionView/Y-White.png\"";
}
}
In View.Cs: I did literally nothing.
In View.xaml: I bind like this:
<StackPanel>
<Image Source="{Binding StatusIconPath} />
</StackPanel>
I still can't see why it is not working. What is that thing that I am missing? Please and thanks.
It did not work to bind the properties directly with the Xaml as recommended. I tried it this way:
VM: sample property:
public BitmapImage VerificationStatusIcon{ get { return new BitmapImage(new Uri(#VerificationStatusIconPath, UriKind.Relative));}}
View Xaml:
<Image Name="verificationStatusImage" Source="{Binding VerificationStatusIcon}" Margin="5,0" Width="40" Height="40"/>
You have a whole bunch of unnecessary characters in your icon paths:
StatusIconPath = "#\"/Resources/SessionView/X-With-Green-Check-White.png\"";
ResultIconPath = "#\"/Resources/SessionView/Y-White.png\"";
Change them to this:
StatusIconPath = "Resources/SessionView/X-With-Green-Check-White.png";
ResultIconPath = "Resources/SessionView/Y-White.png";
. But no images originally to view and no changes..
Verify that the path to the image is correct. Maybe hard code an image to test the control against it.
One other scenario is that the resources are not being copied over for run-time acquisition. Make sure they are actually available during runtime.
can't see why it is not working
Is the main view's DataContext set to the live VM's instance?
What is that thing that I am missing?
If you are sure that the view's datacontext contains the live VM, then make sure that the property StatusIconPath on the VM reports a property change event.
That is so that the XAML control which is bound to it knows that it changed and correspondingly one needs to make sure that the ViewModel which holds StatusIconPath adheres to INotifyPropertyChanged which will facilitate such an operation in general:
private string _StatusIconPath;
public string StatusIconPath
{
get { return _StatusIconPath; }
set
{
_StatusIconPath = value;
PropertyChanged("StatusIconPath");
}
}
I provide more robust example on my blog entitled:
Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding
It turned out that I have an extra unneeded characters in my ImagePaths as Kyle stated. And then, I needed to set my Image.Source from within my View.cs. At least, this is how it worked for me:
ViewModel Something like this:
if (Whatever)
{
StatusIconPath = "/Resources/SessionView/X-With-Green-Check-White.png";
ResultIconPath = "/Resources/SessionView/Y-White.png";
}
Then in View.cs and on SelectedItemChanged:
private void Grid_SelectedItemChanged(object sender, DevExpress.Xpf.Grid.SelectedItemChangedEventArgs e)
{
string tempStatus = ((SessionViewModel) DataContext).StatusIconPath;
string tempResult = ((SessionViewModel) DataContext).ResultIconPath;
StatusImage.Source = new BitmapImage(new Uri(#tempStatus, UriKind.Relative));
ResultImage.Source = new BitmapImage(new Uri(#tempResult, UriKind.Relative));
}
and in Xaml: just a fallback value(any original/default image we want). Ex:
<Image Name="ResultImage" Source="/EZ3D;component/Resources/SessionView/Retake-White.png"/>

Calling UIElement Command from code behind

I am trying to execute a bound command from my code behind utilizing the UiElement. button.Command.Execute(button.CommandParameter)
However, at this point the Command property of the button is null. simultaneously when I check the command in my View Model the property is set. The only diagnosis I can come up with is that until the window is actually visible the command is not bound to the command property of the button. I feel like may I'm missing a step somewhere or my implementation is not sound. below is some snipits of the code, please let me know if you need more.
Window constructor:
public PlottingViewModel ViewModel { get; set; }
public PlottingGUI()
{
InitializeComponent();
DataContext = (ViewModel = new PlottingViewModel());
_setDefaultSelections();
}
IList<RadioButton> buttons;
Setting default selections:
private void _setDefaultSelections()
{
buttons = new List<RadioButton>();
_getRadioButtons(this);
foreach (var setting in ViewModel.Settings.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
var settingValue = setting.GetValue(ViewModel.Settings);
var button = buttons.FirstOrDefault(btn => btn.Content.Equals(settingValue)
|| ((string)btn.CommandParameter).Equals(settingValue));
if (button == null)
continue;
button.IsChecked = true;
// NullReference here
// button.Command.Execute(button.CommandParameter);
}
}
one of the RadioButtons XAML:
<RadioButton Content="None"
Grid.Row="0"
Command="{Binding StampedCommand}"
CommandParameter="None"
Foreground="WhiteSmoke"/>
I feel, the only way i may be able to successfully complete this task is to execute the command directly from my viewmodel. (Which i don't want to do)
Thanks for reading..
To sum up comments at the point when you're calling _setDefaultSelections() bindings have not been updated yet, hence Command is still null, so you have to wait until everything is loaded. You can call _setDefaultSelections during Loaded event
Occurs when the element is laid out, rendered, and ready for interaction.

Add userControl to listbox

I am working on a little project for a contest in my city..and i just hit a brick wall.The thing is: i am creating a userControl in Blend(let's say a canvas,in wich i have a reactangle..a textblock and an image).My problem is that i can not add this to the listboxitem in WPF by code.Addin the userControl one by one in the designer seems to work..but the software is going to work with a variable number of items for the listbox.
private void mainPane1_Loaded(object sender, RoutedEventArgs e)
{
MessageBox.Show("layout updated");
questions cq;
Button btn;
for (int i = 1; i <= 10; i++)
{
btn = new Button();
btn.Content = "intreb" + i.ToString();
cq = new questions();
Canvas.SetZIndex(cq, 17);
//cq.questionHolder.Text = "intrebare" + i.ToString();
listaintrebari.Items.Add(cq);
MessageBox.Show("intrebare" + i.ToString());
listaintrebari.Items.Add(btn);
//MessageBox.Show("layout updated");
}
}
questions is my UserControl and listaintrebari is the listbox.I tried to add some buttons and it works great...but it seems to hate my userControl.
I am waiting for your thoughts on how to resolve this issue, and if you have any sugestions on what other is best to use in my situation and how..it would be great.Thank you!
Ok, here's some actual code that might help you out.
I will be using several WPF concepts that you might want to study further : DataBinding, DataTemplates, ImageSources, ObservableCollections
First you need to create (if you don't have it yet) an underlying class for your Questions. The simplest you can get would be something like this :
internal class Question
{
public ImageSource QuestionImage { get; set; }
public string QuestionText { get; set; }
}
Then in your screen's code behind (yes, we are not at MVVM yet), you should create an ObservableCollection of Question and pouplate them with your questions
I have smth like this:
public ObservableCollection<Question> Questions { get; private set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Questions = new ObservableCollection<Question>();
for (int i = 1; i <= 10; i++)
{
var newQ = new Question { QuestionText = "intrebarea " + i.ToString(), QuestionImage = _get your image as a ImageSource here_ };
Questions.Add(newQ);
}
}
The this.DataContext = this is very important, otherwise your Data Binding will not work.
In your design area, create a list and bind it to the Questions collection you created. The way the question is displayed in the list is driven by the "ItemTemlpate" as below.
<ListBox ItemsSource="{Binding Questions}">
<ListBox.ItemTemplate>
<StackPanel>
<Image Source="{Binding QuestionImage}" Height="20" Width="20"/>
<TextBlock Margin="5" Text="{Binding QuestionText}" />
</StackPanel>
</ListBox.ItemTemplate>
</ListBox>
You can replace the I have there with your UserControl contents or event the UserControl itself, but make sure to preserve the Bindings to the objects in your Question class.
Like I said above, many things might not make sense at this point so make sure you read about them : What is a data Binding, What is a DataContext, What is an ObservableCollection. Also, try looking at MVVM when you get a chance...
Lastly, if you are unsure how to get an ImageSource when you have a jpg or png file in your project:
public ImageSource GetImagesource(string location)
{
var res = new BitmapImage()
res.BeginInit();
res.UriSource = new Uri("pack://application:,,,/ApplicationName;component/" + location);
res.EndInit();
return res;
}
The right way to handle this kind of situation is by having a data model with a collection of your questions. Then bind your ListBox.ItemsSource to the collection and provide a DataTemplate that uses your UserControl.
Use the ListBox's ItemTemplate to define what you want each instance of your object to look like, then bind the ListBox's ItemsSource to a collection of that type.
You need to create a collection (e.g. List) of your control and bind the collection to the ListBox.

silverlight Binding from VM collection to View not working need some help

I know I am missing something here and I could use a pointer. Within a project I have an expander control when this control is clicked it makes a RIA call to a POCO within my project to retreive a second set of data. I am using the SimpleMVVM toolkit here so please let me know if I need to expand on any additional areas.
Within the xaml the expander is laid out as
<toolkit:Expander Header="Name" Style="{StaticResource DetailExpanderSytle}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Expanded">
<ei:CallMethodAction
TargetObject="{Binding Source={StaticResource vm}}"
MethodName="showWarrantNameDetail"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<StackPanel Orientation="Vertical">
<sdk:DataGrid AutoGenerateColumns="true" ItemsSource="{Binding NameResult}" AlternatingRowBackground="Gainsboro" HorizontalAlignment="Stretch" MaxHeight="200">
</sdk:DataGrid>
<local:NameContainer DataContext="{Binding}" />
</StackPanel>
</toolkit:Expander>
I am using the expression Dll coupled with Simple MVVM to get at the methods in the view model vs commands.
Within the view model I have the following code
public void showWarrantNameDetail()
{
//set flags
IsBusy = true;
CanDo = false;
EntityQuery<WarrantNameDataView> query = App.cdContext.GetWarrantNameDataViewsQuery().Where(a => a.PrimaryObjectId == Convert.ToInt32(RecID));
Action<LoadOperation<WarrantNameDataView>> completeProcessing = delegate(LoadOperation<WarrantNameDataView> loadOp)
{
if (!loadOp.HasError)
{
processWarrantNames(loadOp.Entities);
}
else
{
Exception error = loadOp.Error;
}
};
LoadOperation<WarrantNameDataView> loadOperation = App.cdContext.Load(query, completeProcessing, false);
}
private void processWarrantNames(IEnumerable<WarrantNameDataView> entities)
{
ObservableCollection<WarrantNameDataView> NameResult = new ObservableCollection<WarrantNameDataView>(entities);
//we're done
IsBusy = false;
CanDo = true;
}
When I set a break on the processWarrantName I can see the NameResult is set to X number of returns. However within the view the datagrid does not get populated with anything?
Can anyone help me understand what I need to do with the bindings to get the gridview to populate? Other areas of the form which are bound to other collections show data so I know I have the data context of the view set correctly. I've tried both Data context as well as Items Source and no return?
When I set a break on the code the collection is returned as follows so I can see that data is being returned. Any suggestions on what I am missing I would greatly appreciate it.
With regards to the page datacontext I am setting it in the code behind as follows:
var WarrantDetailViewModel = ((ViewModelLocator)App.Current.Resources["Locator"]).WarrantDetailViewModel;
this.DataContext = WarrantDetailViewModel;
this.Resources.Add("vm", WarrantDetailViewModel);
Thanks in advance for any suggestions.
Make ObservableCollection<WarrantNameDataView> NameResult a public property of your ViewModel class. Your view will not be able to bind to something that has a private method scope (or public method scope, or private member scope).
//declaration
public ObservableCollection<WarrantNameDataView> NameResult { get; set }
//in the ViewModel constructor do this
NameResult = new ObservableCollection<WarrantNameDataView>();
//then replace the original line in your method with:
//EDIT: ObservableCollection has no AddRange. Either loop through
//entities and add them to the collection or see OP's answer.
//NameResult.AddRange(entities);
If processWarrantNames gets called more than once, you might need to call NameResult.Clear() before calling AddRange() adding to the collection.
Phil was correct in setting the property to public. One note I'll add is there is no AddRange property in SL or ObservableCollection class that I could find. I was able to assign the entities to the OC using the following code
private ObservableCollection<WarrantNameDataView> warrantNameResult;
public ObservableCollection<WarrantNameDataView> WarrantNameResult
{
get { return warrantNameResult; }
set
{
warrantNameResult = value;
NotifyPropertyChanged(vm => vm.WarrantNameResult);
}
}
and then within the return method
WarrantNameResult = new ObservableCollection<WarrantNameDataView>(entities);
This worked and passed to the UI the collection of data.

Categories

Resources