2008-09-11 76 views
35

在我的WPF應用程序中,我有一些數據綁定文本框。這些綁定的UpdateSourceTriggerLostFocus。該對象使用文件菜單進行保存。我遇到的問題是,可以在文本框中輸入新值,從文件菜單中選擇保存,並且永不保留新值(在文本框中可見的值),因爲訪問菜單不會從文本框中移除焦點。我怎樣才能解決這個問題?有沒有辦法強制頁面中的所有控件都綁定到數據綁定?保存之前的WPF數據綁定

@palehorse:好點。不幸的是,我需要使用LostFocus作爲我的UpdateSourceTrigger以支持我想要的驗證類型。

@dmo:我已經想到了。然而,對於一個相對簡單的問題,它似乎是一個非常不雅的解決方案。此外,它要求在頁面上有一些控件總是可見的以獲得焦點。然而,我的應用程序是選項卡,因此不容易出現這種控制。

@Nidonocu:使用菜單沒有移動焦點從TextBox混淆了我的事實。然而,這是我所看到的行爲。下面的簡單示例演示了我的問題:

<Window x:Class="WpfApplication2.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="Window1" Height="300" Width="300"> 
    <Window.Resources> 
     <ObjectDataProvider x:Key="MyItemProvider" /> 
    </Window.Resources> 
    <DockPanel LastChildFill="True"> 
     <Menu DockPanel.Dock="Top"> 
      <MenuItem Header="File"> 
       <MenuItem Header="Save" Click="MenuItem_Click" /> 
      </MenuItem> 
     </Menu> 
     <StackPanel DataContext="{Binding Source={StaticResource MyItemProvider}}"> 
      <Label Content="Enter some text and then File > Save:" /> 
      <TextBox Text="{Binding ValueA}" /> 
      <TextBox Text="{Binding ValueB}" /> 
     </StackPanel> 
    </DockPanel> 
</Window> 
using System; 
using System.Text; 
using System.Windows; 
using System.Windows.Data; 

namespace WpfApplication2 
{ 
    public partial class Window1 : Window 
    { 
     public MyItem Item 
     { 
      get { return (FindResource("MyItemProvider") as ObjectDataProvider).ObjectInstance as MyItem; } 
      set { (FindResource("MyItemProvider") as ObjectDataProvider).ObjectInstance = value; } 
     } 

     public Window1() 
     { 
      InitializeComponent(); 
      Item = new MyItem(); 
     } 

     private void MenuItem_Click(object sender, RoutedEventArgs e) 
     { 
      MessageBox.Show(string.Format("At the time of saving, the values in the TextBoxes are:\n'{0}'\nand\n'{1}'", Item.ValueA, Item.ValueB)); 
     } 
    } 

    public class MyItem 
    { 
     public string ValueA { get; set; } 
     public string ValueB { get; set; } 
    } 
} 

回答

6

假設您在窗口中有一個文本框,並在其中包含一個保存按鈕的工具欄。假設TextBox的Text屬性綁定到業務對象的屬性,並且綁定的UpdateSourceTrigger屬性設置爲LostFocus的默認值,這意味着當TextBox失去輸入焦點時,綁定值被推回到業務對象屬性。此外,假定ToolBar的保存按鈕的Command屬性設置爲ApplicationCommands.Save命令。

在這種情況下,如果編輯文本框並用鼠標單擊保存按鈕,則會出現問題。當點擊工具欄中的按鈕時,文本框不會失去焦點。由於TextBox的LostFocus事件未觸發,因此Text屬性綁定不會更新業務對象的源屬性。

很顯然,如果用戶界面中最近編輯的值尚未推送到對象中,則不應驗證並保存對象。這是Karl解決的確切問題,方法是在他的窗口中編寫代碼,手動查找帶焦點的TextBox並更新數據綁定的來源。他的解決方案運行良好,但它讓我想到了一種通用的解決方案,這種解決方案在這種特殊情況之外也很有用。輸入CommandGroup ...

從約什 - 史密斯的CodeProject上的文章摘自約CommandGroup

+4

此解決方案僅適用於TextBox。有沒有辦法讓它適用於任何控制? – 2008-09-24 14:13:52

2

您是否嘗試過UpdateSourceTrigger設置的PropertyChanged?或者,你可以調用UpdateSOurce()方法,但是這看起來有點矯枉過正,並且破壞了TwoWay數據綁定的目的。

1

您可以在保存之前將焦點設置在其他地方嗎?

您可以通過調用UI元素上的focus()來做到這一點。

你可以專注於任何元素調用「保存」。如果你的觸發器是LostFocus,那麼你必須把焦點移到某個地方。保存的優點是,它不會被修改,並且對用戶有意義。

0

在研究這個答案時,我有點困惑,你正在看到的行爲正在發生,當然是點擊文件菜單的動作,或者你應該忽略文本框並將其設置到菜單?

7

這是一個醜陋的黑客攻擊,但也應努力

TextBox focusedTextBox = Keyboard.FocusedElement as TextBox; 
if (focusedTextBox != null) 
{ 
    focusedTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource(); 
} 

此代碼檢查,如果一個TextBox具有焦點...如果1被發現...更新綁定源!

+2

+1,這是我現在使用的解決方法。爲了讓它不那麼難看,我建議在調用`UpdateSource`前檢查`null`的綁定表達式(因爲當前未綁定的TextBox可能有焦點)。 – Heinzi 2011-02-09 16:06:35

0

最簡單的方法是將設置爲某個地方的焦點
您可以立即回設定的重點,但將焦點設置在任何地方都會觸發引發LostFocus-事件上任何類型的控制,並使其更新它的東西:

IInputElement x = System.Windows.Input.Keyboard.FocusedElement; 
DummyField.Focus(); 
x.Focus(); 

另一種方式是讓聚焦元素,從焦點元素獲取綁定元素,並手動觸發更新。爲文本框和組合框的例子(你需要添加你需要支持任何控制類型):

TextBox t = Keyboard.FocusedElement as TextBox; 
if ((t != null) && (t.GetBindingExpression(TextBox.TextProperty) != null)) 
    t.GetBindingExpression(TextBox.TextProperty).UpdateSource(); 

ComboBox c = Keyboard.FocusedElement as ComboBox; 
if ((c != null) && (c.GetBindingExpression(ComboBox.TextProperty) != null)) 
    c.GetBindingExpression(ComboBox.TextProperty).UpdateSource(); 
22

我發現,即去掉範圍從菜單中的FocusScope所依賴的菜單項導致文本丟失正確對焦。我不認爲這適用於菜單中的所有項目,但肯定用於保存或驗證操作。

<Menu FocusManager.IsFocusScope="False" > 
+0

這確實解決了原來的問題(與我一樣)。當按下菜單按鈕時,我的setter現在被調用,我可以將LostFocus作爲UpdateSourceTrigger。 – Bob 2010-03-17 19:03:07

+2

對我來說,它看起來像是最好的解決方案。謝謝。 – 2011-06-22 09:48:43

14

假設有在該選項卡序列中的多個控制,下面的解決方案似乎是完整的和一般(僅剪切和粘貼)...

Control currentControl = System.Windows.Input.Keyboard.FocusedElement as Control; 

if (currentControl != null) 
{ 
    // Force focus away from the current control to update its binding source. 
    currentControl.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)); 
    currentControl.Focus(); 
} 
0

你這是什麼想想這個?我相信我已經想出了一種使用反射使其更具通用性的方法。我真的不喜歡像其他一些例子那樣維護一個列表的想法。

var currentControl = System.Windows.Input.Keyboard.FocusedElement; 
if (currentControl != null) 
{ 
    Type type = currentControl.GetType(); 
    if (type.GetMethod("MoveFocus") != null && type.GetMethod("Focus") != null) 
    { 
     try 
     { 
      type.GetMethod("MoveFocus").Invoke(currentControl, new object[] { new TraversalRequest(FocusNavigationDirection.Next) }); 
      type.GetMethod("Focus").Invoke(currentControl, null); 
     } 
     catch (Exception ex) 
     { 
      throw new Exception("Unable to handle unknown type: " + type.Name, ex); 
     } 
    } 
} 

查看這個問題嗎?

3

簡單的解決方案是更新XAML代碼如下所示

<StackPanel DataContext="{Binding Source={StaticResource MyItemProvider}}"> 
     <Label Content="Enter some text and then File > Save:" /> 
     <TextBox Text="{Binding ValueA, UpdateSourceTrigger=PropertyChanged}" /> 
     <TextBox Text="{Binding ValueB, UpdateSourceTrigger=PropertyChanged}" /> 
    </StackPanel> 
0

因爲我注意到這個問題仍然是一個痛苦的屁股來解決一個非常通用的方法,我試過各種解決方案。

最終爲我制定了一個計劃: 每當有必要驗證UI更改並將其更新到其源(檢查關閉窗口時的更改,執行Save操作...)時,我會調用驗證函數,可以執行各種操作: - 確保焦點元素(如文本框,組合框,...)失去焦點,這將觸發默認的更新源行爲 - 驗證賦予驗證的DependencyObject樹中的所有控件功能 - 將焦點設置回原始焦點元素

如果一切順利(驗證成功),函數本身將返回true - >您的原始操作(關閉可選詢問確認,保存,...)可以繼續。否則,該函數將返回false,並且您的操作無法繼續,因爲一個或多個元素(通過元素上的通用ErrorTemplate的幫助)存在驗證錯誤。

碼(驗證功能是基於文章Detecting WPF Validation Errors):

public static class Validator 
{ 
    private static Dictionary<String, List<DependencyProperty>> gdicCachedDependencyProperties = new Dictionary<String, List<DependencyProperty>>(); 

    public static Boolean IsValid(DependencyObject Parent) 
    { 
     // Move focus and reset it to update bindings which or otherwise not processed until losefocus 
     IInputElement lfocusedElement = Keyboard.FocusedElement; 
     if (lfocusedElement != null && lfocusedElement is UIElement) 
     { 
      // Move to previous AND to next InputElement (if your next InputElement is a menu, focus will not be lost -> therefor move in both directions) 
      (lfocusedElement as UIElement).MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous)); 
      (lfocusedElement as UIElement).MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)); 
      Keyboard.ClearFocus(); 
     } 

     if (Parent as UIElement == null || (Parent as UIElement).Visibility != Visibility.Visible) 
      return true; 

     // Validate all the bindings on the parent 
     Boolean lblnIsValid = true; 
     foreach (DependencyProperty aDependencyProperty in GetAllDependencyProperties(Parent)) 
     { 
      if (BindingOperations.IsDataBound(Parent, aDependencyProperty)) 
      { 
       // Get the binding expression base. This way all kinds of bindings (MultiBinding, PropertyBinding, ...) can be updated 
       BindingExpressionBase lbindingExpressionBase = BindingOperations.GetBindingExpressionBase(Parent, aDependencyProperty); 
       if (lbindingExpressionBase != null) 
       { 
        lbindingExpressionBase.ValidateWithoutUpdate(); 
        if (lbindingExpressionBase.HasError) 
         lblnIsValid = false; 
       } 
      } 
     } 

     if (Parent is Visual || Parent is Visual3D) 
     { 
      // Fetch the visual children (in case of templated content, the LogicalTreeHelper will return no childs) 
      Int32 lintVisualChildCount = VisualTreeHelper.GetChildrenCount(Parent); 
      for (Int32 lintVisualChildIndex = 0; lintVisualChildIndex < lintVisualChildCount; lintVisualChildIndex++) 
       if (!IsValid(VisualTreeHelper.GetChild(Parent, lintVisualChildIndex))) 
        lblnIsValid = false; 
     } 

     if (lfocusedElement != null) 
      lfocusedElement.Focus(); 

     return lblnIsValid; 
    } 

    public static List<DependencyProperty> GetAllDependencyProperties(DependencyObject DependencyObject) 
    { 
     Type ltype = DependencyObject.GetType(); 
     if (gdicCachedDependencyProperties.ContainsKey(ltype.FullName)) 
      return gdicCachedDependencyProperties[ltype.FullName]; 

     List<DependencyProperty> llstDependencyProperties = new List<DependencyProperty>(); 
     List<FieldInfo> llstFieldInfos = ltype.GetFields(BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static).Where(Field => Field.FieldType == typeof(DependencyProperty)).ToList(); 
     foreach (FieldInfo aFieldInfo in llstFieldInfos) 
      llstDependencyProperties.Add(aFieldInfo.GetValue(null) as DependencyProperty); 
     gdicCachedDependencyProperties.Add(ltype.FullName, llstDependencyProperties); 

     return llstDependencyProperties; 
    } 
} 
2

我碰到的這個問題,我已經找到了最好的解決辦法是改變按鈕的可聚焦值(或任何其他組件,如菜單項)設置爲true:

<Button Focusable="True" Command="{Binding CustomSaveCommand}"/> 

它的工作原理的原因,是因爲它迫使它調用命令之前的按鈕即可集中,因而使文本框或任何其他的UIElement爲此事放鬆焦點,並提出失去焦點事件調用綁定被改變。

如果你正在使用有界命令(正如我在我的例子中指出的那樣),由於你無法將StaticExtension綁定到有界屬性(也就是DP),因此John Smith的優秀解決方案將不太適合。

0

我正在使用BindingGroup。

XAML:

<R:RibbonWindow Closing="RibbonWindow_Closing" ...> 

    <FrameworkElement.BindingGroup> 
     <BindingGroup /> 
    </FrameworkElement.BindingGroup> 

    ... 
</R:RibbonWindow> 

C#

private void RibbonWindow_Closing(object sender, CancelEventArgs e) { 
    e.Cancel = !NeedSave(); 
} 

bool NeedSave() { 
    BindingGroup.CommitEdit(); 

    // Insert your business code to check modifications. 

    // return true; if Saved/DontSave/NotChanged 
    // return false; if Cancel 
} 

它應該工作。